Skip to content

Commit

Permalink
ethdev+ethface: GTP-U RxFlow with i40e DDP
Browse files Browse the repository at this point in the history
  • Loading branch information
yoursunny committed Jun 27, 2024
1 parent 6457158 commit 9c7fde6
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 32 deletions.
4 changes: 2 additions & 2 deletions csrc/ethface/face.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ EthRxFlow_RxBurst_Checked(RxGroup* rxg, RxGroupBurstCtx* ctx) {

struct rte_flow*
EthFace_SetupFlow(EthFacePriv* priv, const uint16_t queues[], int nQueues, const EthLocator* loc,
bool isolated, struct rte_flow_error* error) {
bool isolated, bool prefersFlowItemGTP, struct rte_flow_error* error) {
EthLocatorClass c = EthLocator_Classify(loc);
NDNDPDK_ASSERT(nQueues > 0 && nQueues <= (int)RTE_DIM(priv->rxf));

struct rte_flow_attr attr = {.ingress = true};

EthFlowPattern pattern;
EthFlowPattern_Prepare(&pattern, loc);
EthFlowPattern_Prepare(&pattern, loc, prefersFlowItemGTP);

struct rte_flow_action_queue queue = {.index = queues[0]};
struct rte_flow_action_rss rss = {
Expand Down
2 changes: 1 addition & 1 deletion csrc/ethface/face.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ typedef struct EthFacePriv {
/** @brief Setup rte_flow on EthDev for hardware dispatching. */
__attribute__((nonnull)) struct rte_flow*
EthFace_SetupFlow(EthFacePriv* priv, const uint16_t queues[], int nQueues, const EthLocator* loc,
bool isolated, struct rte_flow_error* error);
bool isolated, bool prefersFlowItemGTP, struct rte_flow_error* error);

/** @brief Setup RX for memif. */
__attribute__((nonnull)) void
Expand Down
9 changes: 6 additions & 3 deletions csrc/ethface/locator.c
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ EthFlowPattern_Set(EthFlowPattern* flow, size_t i, enum rte_flow_item_type typ,
}

void
EthFlowPattern_Prepare(EthFlowPattern* flow, const EthLocator* loc) {
EthFlowPattern_Prepare(EthFlowPattern* flow, const EthLocator* loc, bool prefersFlowItemGTP) {
EthLocatorClass c = EthLocator_Classify(loc);

*flow = (const EthFlowPattern){0};
Expand Down Expand Up @@ -424,10 +424,13 @@ EthFlowPattern_Prepare(EthFlowPattern* flow, const EthLocator* loc) {
break;
}
case 'G': {
MASK(flow->gtpMask.hdr.msg_type);
MASK(flow->gtpMask.hdr.teid);
PutGtpHdrMinimal(&flow->gtpSpec.hdr, loc->ulTEID);
APPEND(GTP, gtp);
if (prefersFlowItemGTP) {
APPEND(GTP, gtp);
} else {
APPEND(GTPU, gtp);
}
break;
}
}
Expand Down
2 changes: 1 addition & 1 deletion csrc/ethface/locator.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ typedef struct EthFlowPattern {

/** @brief Prepare rte_flow pattern from locator. */
__attribute__((nonnull)) void
EthFlowPattern_Prepare(EthFlowPattern* flow, const EthLocator* loc);
EthFlowPattern_Prepare(EthFlowPattern* flow, const EthLocator* loc, bool prefersFlowItemGTP);

typedef struct EthTxHdr EthTxHdr;

Expand Down
28 changes: 18 additions & 10 deletions docs/hardware.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@ NDN-DPDK aims to work with most Ethernet adapters supported by [DPDK Network Int

The developers have tested NDN-DPDK with the following Ethernet adapters:

model | speed | DPDK driver | RxFlow
-|-|-|-
NVIDIA ConnectX-5 | 100 Gbps | mlx5 | yes
Intel X710 | 10 Gbps | i40e | UDP only
Intel X710 VF | 10 Gbps | iavf | untested
Intel XXV710 | 25 Gbps | i40e | untested
Intel X520 | 10 Gbps | ixgbe | UDP only
Intel I350 | 1 Gbps | igb | no
Broadcom/QLogic 57810 | 10 Gbps | bnx2x | untested
model | speed | DPDK driver | RxFlow Ethernet | RxFlow UDP | RxFlow VXLAN | RxFlow GTP-U
-|-|-|-|-|-|-
NVIDIA ConnectX-5 | 100 Gbps | mlx5 | yes | yes | yes | no
NVIDIA ConnectX-6 | 200 Gbps | mlx5 | yes | yes | yes | yes
Intel X710 | 10 Gbps | i40e | no | yes | no | yes
Intel X710 VF | 10 Gbps | iavf | untested | untested | untested | untested
Intel XXV710 | 25 Gbps | i40e | untested | untested | untested | untested
Intel X520 | 10 Gbps | ixgbe | no | yes | no | untested
Intel I350 | 1 Gbps | igb | no | no | no | untested
Broadcom/QLogic 57810 | 10 Gbps | bnx2x | untested | untested | untested | untested

Some Ethernet adapters have more than one physical ports on the same PCI card.
NDN-DPDK is only tested to work on the first port (lowest PCI address) of those dual-port or quad-port adapters.
Expand Down Expand Up @@ -217,7 +218,6 @@ To use Intel VF:
#### RxFlow Feature

Intel adapters have limited compatibility with NDN-DPDK RxFlow feature.
As tested with i40e and ixgbe drivers, RxFlow can be used with UDP faces, but not Ethernet or VXLAN faces.
You should test RxFlow with your specific hardware and face locators, and decide whether to use this feature.
Example command:

Expand All @@ -234,6 +234,14 @@ ndndpdk-ctrl create-eth-port --pci 04:00.0 --mtu 1500 --rx-flow 16
ndndpdk-ctrl create-eth-port --pci 04:00.0 --mtu 1500
```

GTP-U tunnel face with RxFlow is supported with [I40E poll mode driver](https://doc.dpdk.org/guides/nics/i40e.html) on Intel Ethernet 700 series.
It relies on [Dynamic Device Personalization (DDP)](https://www.intel.com/content/www/us/en/developer/articles/technical/dynamic-device-personalization-for-intel-ethernet-700-series.html) feature.
You must manually download the *GTPv1 DDP profile* and place it at `/lib/firmware/intel/i40e/ddp/gtp.pkg`.
If the profile is found, you would see "upload DDP package success" log message during Ethernet port creation.
Without the profile, GTP-U face creation on RxFlow would fail with "GTP is not supported by default" log message.
During NDN-DPDK service shutdown, a profile rollback will be attempted.
In case of an abnormal shutdown, you may need to power-cycle the server to cleanup the profile.

### Broadcom/QLogic Ethernet Adapters

BCM57810 has been tested with igb\_uio driver.
Expand Down
5 changes: 2 additions & 3 deletions dpdk/ethdev/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import (
type Config struct {
RxQueues []RxQueueConfig
TxQueues []TxQueueConfig
MTU int // if non-zero, change MTU
Promisc bool // promiscuous mode
Conf unsafe.Pointer // pointer to rte_eth_conf, nil means default
MTU int // if non-zero, change MTU
Promisc bool // promiscuous mode
}

// AddRxQueues adds RxQueueConfig for several queues
Expand Down
93 changes: 93 additions & 0 deletions dpdk/ethdev/ddp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package ethdev

/*
#include "../../csrc/core/common.h"
#include <rte_pmd_i40e.h>
#cgo LDFLAGS: -lrte_net_i40e
*/
import "C"
import (
"fmt"
"io"
"os"
"path"
"slices"
"unsafe"

"github.com/usnistgov/ndn-dpdk/dpdk/eal"
"go.uber.org/zap"
)

// DdpProfile represents a Dynamic Device Personalization profile.
type DdpProfile struct {
pkg []byte
info C.struct_rte_pmd_i40e_profile_info
zapPkg zap.Field
}

func (dp *DdpProfile) process(dev EthDev, buf []byte, op C.enum_rte_pmd_i40e_package_op, act string) error {
bufC, bufSize := (*C.uint8_t)(&buf[0]), C.uint32_t(len(buf))
if res := C.rte_pmd_i40e_process_ddp_package(dev.(ethDev).cID(), bufC, bufSize, op); res != 0 {
e := eal.MakeErrno(res)
logger.Error(act+" DDP package error",
dev.ZapField("port"),
dp.zapPkg,
zap.Error(e),
)
return fmt.Errorf("rte_pmd_i40e_process_ddp_package %w", e)
}

logger.Info(act+" DDP package success",
dev.ZapField("port"),
dp.zapPkg,
)
return nil
}

// Upload adds the DDP profile to an i40e device.
// The returned rollback function removes the DDP profile from the device.
func (dp *DdpProfile) Upload(dev EthDev) (rollback func() error, e error) {
// OP_WR_ADD modifies the buffer; OP_WR_DEL expects the modified buffer
buf := slices.Clone(dp.pkg)
if e = dp.process(dev, buf, C.RTE_PMD_I40E_PKG_OP_WR_ADD, "upload"); e != nil {
return nil, e
}
return func() error {
return dp.process(dev, buf, C.RTE_PMD_I40E_PKG_OP_WR_DEL, "rollback")
}, nil
}

// OpenDdpProfile opens a DDP profile from /lib/firmware/intel/i40e/ddp/{}.pkg .
func OpenDdpProfile(name string) (dp *DdpProfile, e error) {
filename := path.Join("/lib/firmware/intel/i40e/ddp", name+".pkg")
logEntry := logger.With(zap.String("filename", filename))
file, e := os.Open(filename)
if e != nil {
logEntry.Warn("open DDP profile error", zap.Error(e))
return nil, e
}
defer file.Close()

dp = &DdpProfile{}
dp.pkg, e = io.ReadAll(file)
if e != nil {
logEntry.Warn("read DDP profile error", zap.Error(e))
return nil, e
}

if res := C.rte_pmd_i40e_get_ddp_info((*C.uint8_t)(&dp.pkg[0]), C.uint32_t(len(dp.pkg)),
(*C.uint8_t)(unsafe.Pointer(&dp.info)), C.uint32_t(unsafe.Sizeof(dp.info)),
C.RTE_PMD_I40E_PKG_INFO_GLOBAL_HEADER); res != 0 {
e = eal.MakeErrno(res)
logEntry.Warn("parse DDP profile error", zap.Error(e))
return nil, fmt.Errorf("rte_pmd_i40e_get_ddp_info %w", e)
}

dp.zapPkg = zap.Dict("pkg",
zap.Uint32("track-id", uint32(dp.info.track_id)),
zap.String("name", C.GoString((*C.char)(unsafe.Pointer(&dp.info.name[0])))),
zap.String("version", fmt.Sprintf("%d.%d.%d.%d",
dp.info.version.major, dp.info.version.minor, dp.info.version.update, dp.info.version.draft)),
)
return dp, nil
}
11 changes: 4 additions & 7 deletions dpdk/ethdev/ethdev.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,11 @@ func (dev ethDev) Start(cfg Config) error {
return fmt.Errorf("%s %w", step, e)
}

conf := (*C.struct_rte_eth_conf)(cfg.Conf)
if conf == nil {
conf = &C.struct_rte_eth_conf{}
conf.rxmode.mtu = C.uint32_t(dev.MTU())
conf.txmode.offloads = C.uint64_t(info.Tx_offload_capa & (txOffloadMultiSegs | txOffloadChecksum))
}
conf := C.struct_rte_eth_conf{}
conf.rxmode.mtu = C.uint32_t(dev.MTU())
conf.txmode.offloads = C.uint64_t(info.Tx_offload_capa & (txOffloadMultiSegs | txOffloadChecksum))

if res := C.rte_eth_dev_configure(dev.cID(), C.uint16_t(len(cfg.RxQueues)), C.uint16_t(len(cfg.TxQueues)), conf); res < 0 {
if res := C.rte_eth_dev_configure(dev.cID(), C.uint16_t(len(cfg.RxQueues)), C.uint16_t(len(cfg.TxQueues)), &conf); res < 0 {
return bail("rte_eth_dev_configure", res)
}

Expand Down
13 changes: 13 additions & 0 deletions dpdk/ethdev/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const (
DriverXDP = "net_af_xdp"
DriverMemif = "net_memif"
DriverRing = "net_ring"
DriverMlx5 = "net_mlx5"
DriverI40e = "net_i40e"
)

const (
Expand Down Expand Up @@ -101,6 +103,17 @@ func (info DevInfo) HasTxChecksumOffload() bool {
return info.Tx_offload_capa&txOffloadChecksum == txOffloadChecksum
}

// PrefersFlowItemGTP indicates the device prefers RTE_FLOW_ITEM_TYPE_GTP to RTE_FLOW_ITEM_TYPE_GTPU.
//
// https://doc.dpdk.org/guides/nics/overview.html rte_flow items availability in networking drivers
func (info DevInfo) PrefersFlowItemGTP() bool {
switch info.Driver() {
case DriverMlx5:
return true
}
return false
}

// MarshalJSON implements json.Marshaler interface.
func (info DevInfo) MarshalJSON() ([]byte, error) {
return infoJSON(info, info.DevInfoC)
Expand Down
14 changes: 13 additions & 1 deletion iface/ethport/port.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ type Port struct {
logger *zap.Logger
dev ethdev.EthDev
devInfo ethdev.DevInfo
ddpRollback func() error
faces map[iface.ID]*Face
rxBouncePool *pktmbuf.Pool
rxImpl rxImpl
Expand Down Expand Up @@ -123,6 +124,11 @@ func (port *Port) closeWithPortsMutex() error {
port.rxImpl = nil
}

if port.ddpRollback != nil {
errs = append(errs, port.ddpRollback())
port.ddpRollback = nil
}

if port.dev != nil {
errs = append(errs, port.dev.Close())
delete(ports, port.dev)
Expand All @@ -142,13 +148,19 @@ func (port *Port) closeWithPortsMutex() error {
return nil
}

func (port *Port) startDev(nRxQueues int, promisc bool) error {
func (port *Port) startDev(nRxQueues int, promisc bool) (e error) {
socket := port.dev.NumaSocket()
rxPool := port.rxBouncePool
if rxPool == nil {
rxPool = ndni.PacketMempool.Get(socket)
}

if port.cfg.RxFlowQueues > 0 && port.devInfo.Driver() == ethdev.DriverI40e {
if dp, e := ethdev.OpenDdpProfile("gtp"); e == nil {
port.ddpRollback, _ = dp.Upload(port.dev)
}
}

cfg := ethdev.Config{
MTU: port.cfg.MTU,
Promisc: promisc,
Expand Down
11 changes: 7 additions & 4 deletions iface/ethport/rxflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ func readFlowErr(e C.struct_rte_flow_error) error {
}

type rxFlow struct {
isolated bool
availQueues []uint16
hasDestroyError bool
isolated bool
prefersFlowItemGTP bool
availQueues []uint16
hasDestroyError bool
}

func (rxFlow) String() string {
Expand Down Expand Up @@ -65,6 +66,7 @@ func (impl *rxFlow) Init(port *Port) error {
if e := impl.setIsolate(port, true); e != nil {
port.logger.Info("flow isolate mode unavailable", zap.Error(e))
}
impl.prefersFlowItemGTP = port.devInfo.PrefersFlowItemGTP()

maxRxQueues := int(port.devInfo.Max_rx_queues)
if port.cfg.RxFlowQueues > maxRxQueues {
Expand Down Expand Up @@ -114,7 +116,8 @@ func (impl *rxFlow) setupFlow(face *Face, queues []uint16) error {
queuesC := (*C.uint16_t)(unsafe.Pointer(unsafe.SliceData(queues)))
locC := face.loc.EthLocatorC()
var flowErr C.struct_rte_flow_error
face.flow = C.EthFace_SetupFlow(face.priv, queuesC, C.int(len(queues)), locC.ptr(), C.bool(impl.isolated), &flowErr)
face.flow = C.EthFace_SetupFlow(face.priv, queuesC, C.int(len(queues)),
locC.ptr(), C.bool(impl.isolated), C.bool(impl.prefersFlowItemGTP), &flowErr)
if face.flow == nil {
return readFlowErr(flowErr)
}
Expand Down

0 comments on commit 9c7fde6

Please sign in to comment.