Commit c5977fb9 authored by Antoine Kaufmann's avatar Antoine Kaufmann Committed by Antoine Kaufmann
Browse files

sims/nic/i40e_bm: PTP support (PHC, rx/tx timestamping)

parent 5e4267ae
......@@ -35,6 +35,7 @@ namespace headers {
#define ETH_TYPE_IP 0x0800
#define ETH_TYPE_ARP 0x0806
#define ETH_TYPE_PTP 0x88F7
struct eth_addr {
uint8_t addr[ETH_ADDR_LEN];
......@@ -177,6 +178,39 @@ struct udp_hdr {
uint16_t chksum;
} __attribute__((packed));
/******************************************************************************/
/* PTP */
struct ptp_v1_hdr {
uint16_t version_ptp;
uint16_t version_network;
uint8_t subdomain;
uint8_t _rsvd0[15];
uint8_t msg_type;
uint8_t src_comm_tech;
uint8_t src_uuid[6];
uint16_t src_portid;
uint16_t seq_id;
uint8_t ctrl;
uint8_t _rsvd1;
uint16_t flags;
} __attribute__((packed));
struct ptp_v2_hdr {
uint8_t msg_type;
uint8_t version_ptp;
uint16_t msg_len;
uint8_t subdomain;
uint8_t _rsvd0;
uint16_t flags;
uint64_t correction;
uint32_t _rsvd1;
uint8_t src_port_id[10];
uint16_t seq_id;
uint8_t ctrl;
uint8_t log_msg_period;
} __attribute__((packed));
/******************************************************************************/
/* whole packets */
......
......@@ -142,7 +142,7 @@ void queue_admin_tx::admin_desc_ctx::process() {
reinterpret_cast<struct i40e_aqc_get_version *>(d->params.raw);
gv->rom_ver = 0;
gv->fw_build = 0;
gv->fw_major = 0;
gv->fw_major = 6;
gv->fw_minor = 0;
gv->api_major = I40E_FW_API_VERSION_MAJOR;
gv->api_minor = I40E_FW_API_VERSION_MINOR_X710;
......
......@@ -40,7 +40,8 @@ i40e_bm::i40e_bm()
pf_atq(*this, regs.pf_atqba, regs.pf_atqlen, regs.pf_atqh, regs.pf_atqt),
hmc(*this),
shram(*this),
lanmgr(*this, NUM_QUEUES) {
lanmgr(*this, NUM_QUEUES),
ptp(*this) {
reset(false);
}
......@@ -425,6 +426,74 @@ uint32_t i40e_bm::reg_mem_read32(uint64_t addr) {
val = regs.glrpb_plw;
break;
case I40E_PRTTSYN_CTL0:
val = regs.prtsyn_ctl_0;
break;
case I40E_PRTTSYN_CTL1:
val = regs.prtsyn_ctl_1;
break;
case I40E_PRTTSYN_AUX_0(0):
val = regs.prtsyn_aux_0;
break;
case I40E_PRTTSYN_STAT_0:
val = regs.prtsyn_stat_0;
regs.prtsyn_stat_0 = 0;
break;
case I40E_PRTTSYN_STAT_1:
val = regs.prtsyn_stat_1;
break;
case I40E_PRTTSYN_ADJ:
val = ptp.adj_get();
break;
/* note the latching behavior prescribed by the spec for the L/H paired
registers: driver must read L first, which will latch the current value
for a future consistent read for h. */
case I40E_PRTTSYN_INC_L:
regs.prtsyn_inc_h = (regs.prtsyn_inc >> 32);
val = regs.prtsyn_inc;
break;
case I40E_PRTTSYN_INC_H:
val = regs.prtsyn_inc_h;
break;
case I40E_PRTTSYN_TIME_L: {
uint64_t phc = ptp.phc_read();
regs.prtsyn_time_h = (phc >> 32);
val = phc;
break;
}
case I40E_PRTTSYN_TIME_H:
val = regs.prtsyn_time_h;
break;
case I40E_PRTTSYN_RXTIME_L(0): /* fallthrough */
case I40E_PRTTSYN_RXTIME_L(1): /* fallthrough */
case I40E_PRTTSYN_RXTIME_L(2): /* fallthrough */
case I40E_PRTTSYN_RXTIME_L(3): {
size_t i = (addr - I40E_PRTTSYN_RXTIME_L(0)) /
(I40E_PRTTSYN_RXTIME_L(1) - I40E_PRTTSYN_RXTIME_L(0));
regs.prtsyn_rxtime_h[i] = (regs.prtsyn_rxtime[i] >> 32);
val = regs.prtsyn_rxtime[i];
break;
}
case I40E_PRTTSYN_RXTIME_H(0): /* fallthrough */
case I40E_PRTTSYN_RXTIME_H(1): /* fallthrough */
case I40E_PRTTSYN_RXTIME_H(2): /* fallthrough */
case I40E_PRTTSYN_RXTIME_H(3): {
size_t i = (addr - I40E_PRTTSYN_RXTIME_H(0)) /
(I40E_PRTTSYN_RXTIME_H(1) - I40E_PRTTSYN_RXTIME_H(0));
val = regs.prtsyn_rxtime_h[i];
regs.prtsyn_rxtime_lock[i] = false;
regs.prtsyn_stat_1 &= ~(1 << (I40E_PRTTSYN_STAT_1_RXT0_SHIFT + i));
break;
}
case I40E_PRTTSYN_TXTIME_L:
regs.prtsyn_txtime_h = (regs.prtsyn_txtime >> 32);
val = regs.prtsyn_txtime;
break;
case I40E_PRTTSYN_TXTIME_H:
val = regs.prtsyn_txtime_h;
break;
default:
#ifdef DEBUG_DEV
log << "unhandled mem read addr=" << addr << logger::endl;
......@@ -640,6 +709,38 @@ void i40e_bm::reg_mem_write32(uint64_t addr, uint32_t val) {
case I40E_GLRPB_PLW:
regs.glrpb_plw = val;
break;
case I40E_PRTTSYN_CTL0:
regs.prtsyn_ctl_0 = val;
break;
case I40E_PRTTSYN_CTL1:
regs.prtsyn_ctl_1 = val;
break;
case I40E_PRTTSYN_AUX_0(0):
regs.prtsyn_aux_0 = val;
break;
case I40E_PRTTSYN_ADJ:
ptp.adj_set(val);
break;
/* note the latching behavior prescribed by the spec for the L/H paired
registers: driver must read L first, which will latch the current value
for a future consistent read for h. */
case I40E_PRTTSYN_INC_L:
regs.prtsyn_inc_l = val;
break;
case I40E_PRTTSYN_INC_H:
regs.prtsyn_inc = (((uint64_t)val) << 32) | regs.prtsyn_inc_l;
ptp.inc_set(regs.prtsyn_inc);
break;
case I40E_PRTTSYN_TIME_L: {
regs.prtsyn_time_l = val;
break;
}
case I40E_PRTTSYN_TIME_H:
ptp.phc_write((((uint64_t)val) << 32) | regs.prtsyn_time_l);
break;
default:
#ifdef DEBUG_DEV
log << "unhandled mem write addr=" << addr << " val=" << val
......@@ -752,6 +853,9 @@ void i40e_bm::reset(bool indicate_done) {
regs.glrpb_ghw = 0xF2000;
regs.glrpb_phw = 0x1246;
regs.glrpb_plw = 0x0846;
regs.prtsyn_ctl_1 = (1 << I40E_PRTTSYN_CTL1_V1MESSTYPE1_SHIFT) |
(1 << I40E_PRTTSYN_CTL1_V2MESSTYPE1_SHIFT);
}
shadow_ram::shadow_ram(i40e_bm &dev_) : dev(dev_), log("sram", dev_) {
......
......@@ -420,7 +420,8 @@ class lan_queue_rx : public lan_queue_base {
public:
explicit rx_desc_ctx(lan_queue_rx &queue_);
virtual void process();
void packet_received(const void *data, size_t len, bool last);
void packet_received(const void *data, size_t len, bool last,
int rxtime_id);
};
uint16_t dbuff_size;
......@@ -432,6 +433,7 @@ class lan_queue_rx : public lan_queue_base {
virtual void initialize();
virtual desc_ctx &desc_ctx_create();
bool ptp_rx_sample(const void *data, size_t len);
public:
lan_queue_rx(lan &lanmgr_, uint32_t &reg_tail, size_t idx, uint32_t &reg_ena,
......@@ -483,6 +485,31 @@ class lan {
void packet_received(const void *data, size_t len);
};
class ptpmgr {
protected:
static const uint64_t CLOCK_HZ = 625000000;
i40e_bm &dev;
uint64_t last_updated;
__uint128_t last_val;
int64_t offset;
uint64_t inc_val;
bool adj_neg;
uint32_t adj_val;
uint64_t update_clock();
public:
explicit ptpmgr(i40e_bm &dev);
uint64_t phc_read();
void phc_write(uint64_t val);
uint32_t adj_get();
void adj_set(uint32_t val);
void inc_set(uint64_t inc);
};
class shadow_ram {
protected:
i40e_bm &dev;
......@@ -579,6 +606,24 @@ class i40e_bm : public nicbm::Runner::Device {
uint32_t glrpb_glw;
uint32_t glrpb_phw;
uint32_t glrpb_plw;
uint32_t prtsyn_ctl_0;
uint32_t prtsyn_ctl_1;
uint32_t prtsyn_aux_0;
uint32_t prtsyn_stat_0;
uint32_t prtsyn_stat_1;
uint64_t prtsyn_inc;
uint32_t prtsyn_inc_l;
uint32_t prtsyn_inc_h;
uint32_t prtsyn_time_l;
uint32_t prtsyn_time_h;
bool prtsyn_rxtime_lock[4];
uint64_t prtsyn_rxtime[4];
uint32_t prtsyn_rxtime_h[4];
uint64_t prtsyn_txtime;
uint32_t prtsyn_txtime_h;
};
public:
......@@ -604,6 +649,7 @@ class i40e_bm : public nicbm::Runner::Device {
host_mem_cache hmc;
shadow_ram shram;
lan lanmgr;
ptpmgr ptp;
int_ev intevs[NUM_PFINTS];
......
......@@ -305,6 +305,93 @@ queue_base::desc_ctx &lan_queue_rx::desc_ctx_create() {
return *new rx_desc_ctx(*this);
}
/* determine if we should sample a ptp rx timestamp for this packet */
bool lan_queue_rx::ptp_rx_sample(const void *data, size_t len) {
if (!(dev.regs.prtsyn_ctl_1 & I40E_PRTTSYN_CTL1_TSYNENA_MASK))
return false;
uint8_t tsyntype =
(dev.regs.prtsyn_ctl_1 & I40E_PRTTSYN_CTL1_TSYNTYPE_MASK) >>
I40E_PRTTSYN_CTL1_TSYNTYPE_SHIFT;
uint8_t udp_ena = (dev.regs.prtsyn_ctl_1 & I40E_PRTTSYN_CTL1_UDP_ENA_MASK) >>
I40E_PRTTSYN_CTL1_UDP_ENA_SHIFT;
uint8_t v1messtype0 =
(dev.regs.prtsyn_ctl_1 & I40E_PRTTSYN_CTL1_V1MESSTYPE0_MASK) >>
I40E_PRTTSYN_CTL1_V1MESSTYPE0_SHIFT;
uint8_t v1messtype1 =
(dev.regs.prtsyn_ctl_1 & I40E_PRTTSYN_CTL1_V1MESSTYPE1_MASK) >>
I40E_PRTTSYN_CTL1_V1MESSTYPE1_SHIFT;
uint8_t v2messtype0 =
(dev.regs.prtsyn_ctl_1 & I40E_PRTTSYN_CTL1_V2MESSTYPE0_MASK) >>
I40E_PRTTSYN_CTL1_V2MESSTYPE0_SHIFT;
uint8_t v2messtype1 =
(dev.regs.prtsyn_ctl_1 & I40E_PRTTSYN_CTL1_V2MESSTYPE1_MASK) >>
I40E_PRTTSYN_CTL1_V2MESSTYPE1_SHIFT;
const headers::pkt_udp *udp =
reinterpret_cast<const headers::pkt_udp *>(data);
const void *ptp_start;
bool is_udp = false;
// TODO(antoinek): ipv6
if (udp->eth.type == htons(ETH_TYPE_IP) && udp->ip.proto == IP_PROTO_UDP) {
// no udp packet types enabled
if (tsyntype == 0)
return false;
uint16_t port = ntohs(udp->udp.dest);
if (!(port == 0x013F && (udp_ena == 1 || udp_ena == 3)) &&
!(port == 0x0140 && (udp_ena == 2 || udp_ena == 3)))
return false;
is_udp = true;
ptp_start = udp + 1;
} else if (udp->eth.type == htons(ETH_TYPE_PTP)) {
if (tsyntype == 1)
return false;
ptp_start = &udp->ip;
} else {
return false;
}
const headers::ptp_v1_hdr *v1 =
reinterpret_cast<const headers::ptp_v1_hdr *>(ptp_start);
const headers::ptp_v2_hdr *v2 =
reinterpret_cast<const headers::ptp_v2_hdr *>(ptp_start);
/* handle ptp v1 */
if (v1->version_ptp == 1) {
/* check if v1 is allowed*/
if (tsyntype != 1)
return false;
if (v1messtype0 == 0xff || v1messtype1 == 0xff)
return true;
if (v1->msg_type == v1messtype0 || v1->msg_type == v1messtype1)
return true;
return false;
} else if (v2->version_ptp == 2) {
/* check if no v1 packets allowed*/
if (tsyntype == 1)
return false;
if (tsyntype == 0 && is_udp)
return false;
if (tsyntype == 3)
return v2->msg_type < 8;
if (v2messtype0 == 0xf)
return true;
if (v2->msg_type == v2messtype0 || v2->msg_type == v2messtype1)
return true;
return false;
}
return false;
}
void lan_queue_rx::packet_received(const void *data, size_t pktlen,
uint32_t h) {
size_t num_descs = (pktlen + dbuff_size - 1) / dbuff_size;
......@@ -320,6 +407,21 @@ void lan_queue_rx::packet_received(const void *data, size_t pktlen,
return;
}
// sample ptp rx timestamp if enabled, packet matches conditions, and a free
// rx timestamp register can be found.
int rxtime_id = -1;
if (ptp_rx_sample(data, pktlen)) {
for (int i = 0; i < 4; i++) {
if (!dev.regs.prtsyn_rxtime_lock[i]) {
dev.regs.prtsyn_rxtime[i] = dev.ptp.phc_read();
dev.regs.prtsyn_rxtime_lock[i] = true;
dev.regs.prtsyn_stat_1 |= 1 << (I40E_PRTTSYN_STAT_1_RXT0_SHIFT + i);
rxtime_id = i;
break;
}
}
}
for (size_t i = 0; i < num_descs; i++) {
rx_desc_ctx &ctx = *dcache.front();
......@@ -332,9 +434,9 @@ void lan_queue_rx::packet_received(const void *data, size_t pktlen,
const uint8_t *buf = (const uint8_t *)data + (dbuff_size * i);
if (i == num_descs - 1) {
// last packet
ctx.packet_received(buf, pktlen - dbuff_size * i, true);
ctx.packet_received(buf, pktlen - dbuff_size * i, true, rxtime_id);
} else {
ctx.packet_received(buf, dbuff_size, false);
ctx.packet_received(buf, dbuff_size, false, -1);
}
}
}
......@@ -352,7 +454,7 @@ void lan_queue_rx::rx_desc_ctx::process() {
}
void lan_queue_rx::rx_desc_ctx::packet_received(const void *data, size_t pktlen,
bool last) {
bool last, int rxtime_id) {
// we only use fields in the lowest 16b anyways, even if set to 32b
union i40e_16byte_rx_desc *rxd =
reinterpret_cast<union i40e_16byte_rx_desc *>(desc);
......@@ -371,6 +473,15 @@ void lan_queue_rx::rx_desc_ctx::packet_received(const void *data, size_t pktlen,
rxd->wb.qword1.status_error_len |= (1 << I40E_RX_DESC_STATUS_EOF_SHIFT);
// TODO(antoinek): only if checksums are correct
rxd->wb.qword1.status_error_len |= (1 << I40E_RX_DESC_STATUS_L3L4P_SHIFT);
// if an rx timestamp was assigned, need to report ts register id in
// descriptor
if (rxtime_id >= 0) {
rxd->wb.qword1.status_error_len |= ((uint32_t)rxtime_id)
<< I40E_RX_DESC_STATUS_TSYNINDX_SHIFT;
rxd->wb.qword1.status_error_len |=
(1 << I40E_RX_DESC_STATUS_TSYNVALID_SHIFT);
}
}
data_write(addr, pktlen, data);
}
......@@ -444,6 +555,7 @@ bool lan_queue_tx::trigger_tx_packet() {
uint64_t d1;
uint32_t iipt, l4t, pkt_len, total_len = 0, data_limit;
bool tso = false;
bool tsyn = false;
uint32_t tso_mss = 0, tso_paylen = 0;
uint16_t maclen = 0, iplen = 0, l4len = 0;
......@@ -470,6 +582,7 @@ bool lan_queue_tx::trigger_tx_packet() {
((d1 & I40E_TXD_CTX_QW1_CMD_MASK) >> I40E_TXD_CTX_QW1_CMD_SHIFT);
tso = !!(cmd & I40E_TX_CTX_DESC_TSO);
tso_mss = (d1 & I40E_TXD_CTX_QW1_MSS_MASK) >> I40E_TXD_CTX_QW1_MSS_SHIFT;
tsyn = !!(cmd & I40E_TX_CTX_DESC_TSYN);
#ifdef DEBUG_LAN
log << " tso=" << tso << " mss=" << tso_mss << logger::endl;
......@@ -625,6 +738,19 @@ bool lan_queue_tx::trigger_tx_packet() {
}
}
// PTP transmit timestamping
if (tsyn) {
dev.regs.prtsyn_txtime = dev.ptp.phc_read();
lanmgr.dev.regs.prtsyn_stat_0 |= I40E_PRTTSYN_STAT_0_TXTIME_MASK;
if ((dev.regs.prtsyn_ctl_0 & I40E_PRTTSYN_CTL0_TXTIME_INT_ENA_MASK) &&
(lanmgr.dev.regs.pfint_icr0_ena & I40E_PFINT_ICR0_ENA_TIMESYNC_MASK)) {
lanmgr.dev.regs.pfint_icr0 |=
I40E_PFINT_ICR0_INTEVENT_MASK | I40E_PFINT_ICR0_TIMESYNC_MASK;
lanmgr.dev.SignalInterrupt(0, 0);
}
}
#ifdef DEBUG_LAN
log << " unit done" << logger::endl;
#endif
......
/*
* Copyright 2024 Max Planck Institute for Software Systems, and
* National University of Singapore
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include <cassert>
#include <iostream>
#include "sims/nic/i40e_bm/i40e_base_wrapper.h"
#include "sims/nic/i40e_bm/i40e_bm.h"
namespace i40e {
ptpmgr::ptpmgr(i40e_bm &dev_)
: dev(dev_),
last_updated(0),
last_val(0),
offset(0),
inc_val(0),
adj_neg(false),
adj_val(0) {
}
uint64_t ptpmgr::update_clock() {
/* this simulates the behavior of the PHC, but instead of doing it cycle by
cycle, we calculate updates when the clock is accessed or parameters are
modified, applying the same changes that should have happened cycle by
cycle. Before modifying any of the parameters update_clock has to be
called to get the correct behavior, to ensure e.g. that updates to adj and
inc are applied at the correct points in time.*/
uint64_t ps_per_cycle = 1000000000000ULL / CLOCK_HZ;
uint64_t cycle_now = dev.runner_->TimePs() / ps_per_cycle;
uint64_t cycles_passed = cycle_now - last_updated;
// increment clock
last_val += (__uint128_t)inc_val * cycles_passed;
// factor in adjustments
if (adj_val != 0) {
__uint128_t adj;
if (adj_val <= cycles_passed) {
adj = cycles_passed;
adj_val -= cycles_passed;
} else {
adj = adj_val;
adj_val = 0;
}
adj = adj << 32;
if (adj_neg)
last_val -= adj;
else
last_val += adj;
}
last_updated = cycle_now;
return (last_val >> 32) + offset;
}
uint64_t ptpmgr::phc_read() {
return update_clock();
}
void ptpmgr::phc_write(uint64_t val) {
uint64_t cur_val = update_clock();
offset += (val - cur_val);
}
uint32_t ptpmgr::adj_get() {
update_clock();
uint32_t x = (adj_val << I40E_PRTTSYN_ADJ_TSYNADJ_SHIFT) &
I40E_PRTTSYN_ADJ_TSYNADJ_MASK;
if (adj_neg)
x |= I40E_PRTTSYN_ADJ_SIGN_MASK;
return x;
}
void ptpmgr::adj_set(uint32_t val) {
update_clock();
adj_val =
(val & I40E_PRTTSYN_ADJ_TSYNADJ_MASK) >> I40E_PRTTSYN_ADJ_TSYNADJ_SHIFT;
adj_neg = (val & I40E_PRTTSYN_ADJ_SIGN_MASK);
}
void ptpmgr::inc_set(uint64_t inc) {
update_clock();
inc_val = inc;
}
} // namespace i40e
......@@ -25,7 +25,7 @@ include mk/subdir_pre.mk
bin_i40e_bm := $(d)i40e_bm
OBJS := $(addprefix $(d),i40e_bm.o i40e_queues.o i40e_adminq.o i40e_hmc.o \
i40e_lan.o xsums.o rss.o logger.o)
i40e_lan.o i40e_ptp.o xsums.o rss.o logger.o)
$(OBJS): CPPFLAGS := $(CPPFLAGS) -I$(d)include/
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment