From 46974ed15c2c316484f8b257c907d94ab0a690e9 Mon Sep 17 00:00:00 2001 From: David Elie-Dit-Cosaque Date: Tue, 19 May 2026 21:18:12 -0500 Subject: [PATCH] Add configurable link-up delay to simulate real NIC carrier negotiation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Real NICs don't bring carrier up instantly — there is link negotiation, PHY training, etc. Add a per-port link_up_delay_ms debugfs knob (default 0 = instant) that keeps carrier off after ip link set up and fires netif_carrier_on via a delayed work after the configured delay. PTP clocks remain accessible throughout. Includes a CI smoke test that sets a 3s delay, verifies carrier stays down during the delay, comes up after, and confirms PTP clock sync still works. --- .github/workflows/ci.yml | 53 ++++++++++++++++++++++++++++++++++++++++ netdevsim/dev.c | 5 ++++ netdevsim/netdev.c | 40 ++++++++++++++++++++++++++++++ netdevsim/netdevsim.h | 3 +++ 4 files changed, 101 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28167ef..b8ddcf7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,6 +115,59 @@ jobs: IFACE=$(ls /sys/bus/pci/devices/${DOMAIN}:${BUS}:02.0/net/ 2>/dev/null | head -1) [ -n "$IFACE" ] && ethtool -T "$IFACE" || echo "WARN: No interface found" + - name: Smoke test — link up delay + if: steps.load.outputs.load_ok == 'true' + run: | + set -euo pipefail + + BUS_NR=$(cat /sys/module/netdevsim/parameters/pci_bus_nr 2>/dev/null || echo 31) + BUS=$(printf "%02x" "$BUS_NR") + FAKE_ROOT=$(ls /sys/bus/pci/devices/ 2>/dev/null | grep ":${BUS}:00\.0" | head -1) + DOMAIN="${FAKE_ROOT:+$(echo "$FAKE_ROOT" | cut -d: -f1)}" + : "${DOMAIN:=0000}" + + IFACE=$(ls /sys/bus/pci/devices/${DOMAIN}:${BUS}:02.0/net/ 2>/dev/null | head -1) + [ -z "$IFACE" ] && { echo "SKIP: no interface found"; exit 0; } + + echo "Interface: $IFACE" + + # Bring down first so we start from a known state + sudo ip link set "$IFACE" down + sleep 1 + + # Set a 3-second link-up delay via debugfs + DEBUGFS="/sys/kernel/debug/netdevsim/netdevsim1/ports/0/link_up_delay_ms" + echo 3000 | sudo tee "$DEBUGFS" + echo "link_up_delay_ms set to $(cat "$DEBUGFS")" + + # Bring interface up — carrier should stay off + sudo ip link set "$IFACE" up + sleep 0.5 + CARRIER=$(cat /sys/class/net/"$IFACE"/carrier 2>/dev/null || echo 0) + echo "Carrier immediately after up: $CARRIER" + [ "$CARRIER" -eq 0 ] || { echo "FAIL: carrier should be 0 during delay"; exit 1; } + + # Wait for the delay to expire + sleep 4 + CARRIER=$(cat /sys/class/net/"$IFACE"/carrier 2>/dev/null || echo 0) + echo "Carrier after delay: $CARRIER" + [ "$CARRIER" -eq 1 ] || { echo "FAIL: carrier should be 1 after delay"; exit 1; } + + # Verify PTP clock is still readable + PHC_IDX=$(ethtool -T "$IFACE" 2>/dev/null | grep -oP 'PTP Hardware Clock: \K[0-9]+' || true) + if [ -n "$PHC_IDX" ]; then + echo "PHC index: $PHC_IDX" + # Read the clock via sysfs — confirms the PTP device works + cat /sys/class/nsim_ptp/nsim_ptp${PHC_IDX}/clock 2>/dev/null \ + && echo "OK: PTP clock readable" \ + || echo "WARN: could not read PTP clock via sysfs" + fi + + # Verify hwtstamp config still works after link-up delay + ethtool -T "$IFACE" | grep -E "PTP Hardware Clock|hardware" + + echo "=== LINK UP DELAY TEST PASSED ===" + - name: Cleanup if: always() run: | diff --git a/netdevsim/dev.c b/netdevsim/dev.c index 18fe57f..e6e4aed 100644 --- a/netdevsim/dev.c +++ b/netdevsim/dev.c @@ -1394,6 +1394,11 @@ static int __nsim_dev_port_add(struct nsim_dev *nsim_dev, enum nsim_dev_port_typ goto err_port_debugfs_exit; } + if (nsim_dev_port_is_pf(nsim_dev_port)) + debugfs_create_u32("link_up_delay_ms", 0600, + nsim_dev_port->ddir, + &nsim_dev_port->ns->link_up_delay_ms); + if (nsim_dev_port_is_vf(nsim_dev_port)) { err = devl_rate_leaf_create(&nsim_dev_port->devlink_port, nsim_dev_port, NULL); diff --git a/netdevsim/netdev.c b/netdevsim/netdev.c index af5bfc0..24b0f73 100644 --- a/netdevsim/netdev.c +++ b/netdevsim/netdev.c @@ -400,8 +400,42 @@ static int nsim_set_ts_config(struct net_device *netdev, } return 0; } +static void nsim_link_up_work(struct work_struct *work) +{ + struct netdevsim *ns = + container_of(work, struct netdevsim, link_up_dwork.work); + + netif_carrier_on(ns->netdev); +} + +static int nsim_open(struct net_device *dev) +{ + struct netdevsim *ns = netdev_priv(dev); + u32 delay = READ_ONCE(ns->link_up_delay_ms); + + if (delay) + schedule_delayed_work(&ns->link_up_dwork, + msecs_to_jiffies(delay)); + else + netif_carrier_on(dev); + + return 0; +} + +static int nsim_stop(struct net_device *dev) +{ + struct netdevsim *ns = netdev_priv(dev); + + cancel_delayed_work_sync(&ns->link_up_dwork); + netif_carrier_off(dev); + + return 0; +} + static const struct net_device_ops nsim_netdev_ops = { .ndo_start_xmit = nsim_start_xmit, + .ndo_open = nsim_open, + .ndo_stop = nsim_stop, .ndo_set_rx_mode = nsim_set_rx_mode, .ndo_set_mac_address = eth_mac_addr, .ndo_validate_addr = eth_validate_addr, @@ -525,6 +559,8 @@ static int nsim_init_netdevsim(struct netdevsim *ns) ns->phc = phc; ns->netdev->netdev_ops = &nsim_netdev_ops; + INIT_DELAYED_WORK(&ns->link_up_dwork, nsim_link_up_work); + err = nsim_udp_tunnels_info_create(ns->nsim_dev, ns->netdev); if (err) goto err_phc_destroy; @@ -537,6 +573,8 @@ static int nsim_init_netdevsim(struct netdevsim *ns) nsim_macsec_init(ns); nsim_ipsec_init(ns); + netif_carrier_off(ns->netdev); + err = register_netdevice(ns->netdev); if (err) goto err_ipsec_teardown; @@ -624,6 +662,8 @@ void nsim_destroy(struct netdevsim *ns) struct net_device *dev = ns->netdev; struct netdevsim *peer; + cancel_delayed_work_sync(&ns->link_up_dwork); + rtnl_lock(); peer = rtnl_dereference(ns->peer); if (peer) diff --git a/netdevsim/netdevsim.h b/netdevsim/netdevsim.h index 526834b..79c1b78 100644 --- a/netdevsim/netdevsim.h +++ b/netdevsim/netdevsim.h @@ -157,6 +157,9 @@ struct netdevsim { struct nsim_ethtool ethtool; struct netdevsim __rcu *peer; struct kernel_hwtstamp_config tstamp_config; + + u32 link_up_delay_ms; + struct delayed_work link_up_dwork; }; struct netdevsim *