diff --git a/docs/modules/wiitek/README.md b/docs/modules/wiitek/README.md index 9ed2669..7866c64 100644 --- a/docs/modules/wiitek/README.md +++ b/docs/modules/wiitek/README.md @@ -40,6 +40,8 @@ cabletest drives this module through its own Go RollBall client (`phy.go`): modu Not available in safe space: pre-FEC counter, cable length — those live in the µC vendor space that killed the modules. +**A down port hides the PHY: TX_DISABLE powers the core down, not the µC.** ixgbe qualifies this module as fiber (the fake-SR EEPROM) and asserts TX_DISABLE while the netdev is admin-down; the module then answers every PHY register read with 0xFFFF through a fully working, echo-matched mailbox — EEPROM reads are unaffected. Proven by flipping admin state on the bench: down = PHY ID 0xFFFFFFFF, and the core wakes within 0.25 s of the raise (measured 0.07–0.24 s across cycles; the BCM answers instantly). The dev host masks it (its unmanaged config leaves the ports up from boot); a fresh appliance boot hits it on a never-raised port. cabletest therefore raises the links before first touching a module, and settles identify through a short bounded wait while the core wakes. + **Mailbox latency is regime-dependent** — <25 ms per read linked (X520 host), ~220 ms steady with no link (bench-measured, `~/work/phydiag-work/rb_mbox_latency.py`), and beyond 3 s while 10GBASE-T training runs (observed live when a cable plug landed mid-read at bringup). cabletest's mailbox deadline (`rbCmdTimeout`) is sized past a full ~6 s training so a plug event reads busy, not dead. **Mailbox trap: an orphaned command's completion answers as yours.** If a session dies mid-mailbox-op (killed process), the µC finishes the orphaned command on its own time, leaving CMD=DONE and that command's value in the block. A next session that issues a command and samples status immediately reads the orphan's DONE and value as its own — seen live: a PHY ID 1.2 read answered 0x0006, the orphaned EEE-advert (7.60) value of the killed session, while the very next read was correct. The client therefore never samples status before a full poll gap, reads the value from the same 6-byte block read as the status, and accepts a completion only when the block echoes the issued devad/reg (`phy.go` mbox). The split-transaction python clients always had the pre-sample gap, which is why the field campaign never hit this. diff --git a/docs/nics/x520/README.md b/docs/nics/x520/README.md index 3aedad3..34c1a1e 100644 --- a/docs/nics/x520/README.md +++ b/docs/nics/x520/README.md @@ -28,16 +28,16 @@ What this design carries in practice: ## `sff_i2c` diagnostics transport (patched driver, validated) -Patched ixgbe (`~/work/ixgbe-sff/`) adds `ixgbe_i2c_raw_write`/`ixgbe_i2c_raw_read` (`ixgbe_phy.c`) — arbitrary START…STOP transactions from the driver's bit-bang primitives, swfw-semaphore bracketed — behind `/sys/kernel/debug/ixgbe//sff_i2c` (`w ` / `r `). +Patched ixgbe (`~/work/ixgbe-sff/`) adds `ixgbe_i2c_raw_write`/`ixgbe_i2c_raw_read` (`ixgbe_phy.c`) — arbitrary START…STOP transactions from the driver's bit-bang primitives, swfw-semaphore bracketed — behind `/sys/kernel/debug/ixgbe//sff_i2c` (`w ` / `r `). The product subset of the patch (this transport, the ETQF steering below, the FCoE slot guard) is versioned as `kernel/ixgbe-sff.patch` and applied by `mkimage` to the appliance kernel. - **SDA-release fix (required)**: multi-byte reads returned only byte 0 — the master's ACK left SDA driven low and nothing released it (stock 82599 paths never clock in more than one byte, so the missing release was invisible). `raw_read` releases SDA after each ACK, mirroring stock's release block after clocking out a byte. - **Two-master windows closed by the compound op**: each simple `w`/`r` takes the semaphore separately, so multi-op protocol sequences have unlocked gaps where the driver's event-driven I2C (SFP identify, link setup, `ethtool -m`) can interleave — traffic that fires exactly around cable swaps, when diagnostics also run, so collisions are rare but correlated with the interesting moments. Serialization itself holds (driver SFP traffic uses the same byte primitives under the same `phy_semaphore_mask`), and AT24-style word pointers do persist across STOP — the hazard is the other master *moving* the pointer or consuming the BCM 0x56 bridge's pending read data. The `x ` command (`ixgbe_i2c_raw_xfer`) runs write→STOP→in-kernel delay→read under a single swfw hold, closing the window by construction; the write/read primitives are split into locked/unlocked halves so `x` reuses them under one acquire. - **Compound op proven on the FS BCM** (`~/work/phydiag-work/compound_test.py`): PHY ID and cmd STATUS read identically through the single-hold op and the old split sequence (0x3590 / 0x5081 / 0x0004); a ~3 ms hold is shorter than the driver's own SFP-identify holds. The delay sweep corroborates the hazard the op closes: at delay 0 the bridged read returns stale data (the prior read's 0x0004), at ≥200 µs it reads correctly. The >1 ms BCM delay is validated at 3 ms; treat 0x0000 reads as retry-with-longer-delay — 0 is also the bridge's not-ready signature. - **RollBall split transactions are field-proven** — the i40e oracle path drove RollBall entirely with single-byte, separate-STOP transactions on both the Fibergaga and the original Wiiteks; the kernel's combined form is not required by the modules. -## Register/timestamp bench transport (patched driver) +## Register/timestamp bench transport (bench build only) -For the timestamp/counter bench pokes the driver also carries `reg_ops` `read ` returning the value through the file (not just dmesg) and a `ts_bench` command running the latch poll/re-arm loop in-kernel (`arm `, `control`, `latchrate `) — needed because the BAR is unmappable from userspace under `IO_STRICT_DEVMEM`. Poke driver: `~/work/phydiag-work/x520poke`. +The out-of-tree bench build additionally carries `reg_ops` `read ` returning the value through the file (not just dmesg) and a `ts_bench` command running the latch poll/re-arm loop in-kernel (`arm `, `control`, `latchrate `) — needed because the BAR is unmappable from userspace under `IO_STRICT_DEVMEM`. These serve the concluded timestamp investigation and are deliberately not in the appliance patch. Poke driver: `~/work/phydiag-work/x520poke`. ## Timestamping facts diff --git a/docs/state.md b/docs/state.md index 59aa6f3..5281664 100644 --- a/docs/state.md +++ b/docs/state.md @@ -39,9 +39,7 @@ Indices shift as stashes are pushed/popped — match by message, not number. ## Diagnostics artifacts outside the repo -In `~/work/`, ready to fold into the repo's `kernel/`: - -- **Patched ixgbe** (`~/work/ixgbe-sff/`) — the `sff_i2c` raw-I2C debugfs transport, ETQF steering, `reg_ops`/`ts_bench`; `./load-ixgbe` rebuilds + swaps the module with `allow_unsupported_sfp=1`. Details: nics/x520/. +- **Patched ixgbe, bench build** (`~/work/ixgbe-sff/`) — out-of-tree module for the dev host: the product patch plus the bench-only extras (`reg_ops` value readback, `ts_bench`); `./load-ixgbe` rebuilds + swaps the module with `allow_unsupported_sfp=1`. The product subset (raw I2C + `sff_i2c`, ETQF steering, FCoE slot guard) is versioned at `kernel/ixgbe-sff.patch` and applied by `./mkimage` to every appliance kernel build — apply fails loudly when stable moves under it; port the patch then. Details: nics/x520/. - **Bench tools and clients** (`~/work/phydiag-work/`) — `etqfbench/`, `x520poke/`, `compound_test.py`, `bcm_mcia_emu.py`, `bcm_ixgbe.py`, `bcm_explore.py`/`bcm_eee_off.py`, `bcm_ecd_probe.py`, `bcm_phaselock_bench.py`, `rollball_ixgbe.py`, `rb_forensic.py`. What each proved lives in modules/fs/, modules/wiitek/ and nics/x520/. ## Every boot diff --git a/kernel/ixgbe-sff.patch b/kernel/ixgbe-sff.patch new file mode 100644 index 0000000..17b57d3 --- /dev/null +++ b/kernel/ixgbe-sff.patch @@ -0,0 +1,571 @@ +diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe.h b/drivers/net/ethernet/intel/ixgbe/ixgbe.h +index 9b8217523fd2..15d77ccb749d 100644 +--- a/drivers/net/ethernet/intel/ixgbe/ixgbe.h ++++ b/drivers/net/ethernet/intel/ixgbe/ixgbe.h +@@ -771,6 +771,13 @@ struct ixgbe_adapter { + u32 atr_sample_rate; + spinlock_t fdir_perfect_lock; + ++ struct ixgbe_etqf_entry { ++ bool used; ++ u32 loc; ++ u16 etype; ++ u8 ring; ++ } etqf_filters[IXGBE_MAX_ETQF_FILTERS]; ++ + bool fw_emp_reset_disabled; + + #ifdef IXGBE_FCOE +@@ -1010,6 +1017,8 @@ void ixgbe_atr_compute_perfect_hash_82599(union ixgbe_atr_input *input, + int ixgbe_update_ethtool_fdir_entry(struct ixgbe_adapter *adapter, + struct ixgbe_fdir_filter *input, + u16 sw_idx); ++void ixgbe_etqf_filter_restore(struct ixgbe_adapter *adapter); ++void ixgbe_etqf_filter_exit(struct ixgbe_adapter *adapter); + void ixgbe_set_rx_mode(struct net_device *netdev); + #ifdef CONFIG_IXGBE_DCB + void ixgbe_set_rx_drop_en(struct ixgbe_adapter *adapter); +diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_debugfs.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_debugfs.c +index 5b1cf49df3d3..75689ca7eb2d 100644 +--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_debugfs.c ++++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_debugfs.c +@@ -5,6 +5,7 @@ + #include + + #include "ixgbe.h" ++#include "ixgbe_phy.h" + + static struct dentry *ixgbe_dbg_root; + +@@ -185,6 +186,123 @@ static const struct file_operations ixgbe_dbg_netdev_ops_fops = { + .write = ixgbe_dbg_netdev_ops_write, + }; + ++#define IXGBE_SFF_I2C_MAX 32 ++ ++struct ixgbe_sff_i2c_ctx { ++ struct ixgbe_adapter *adapter; ++ int resp_len; ++ char resp[IXGBE_SFF_I2C_MAX * 3 + 16]; ++}; ++ ++static int ixgbe_dbg_sff_i2c_open(struct inode *inode, struct file *filp) ++{ ++ struct ixgbe_sff_i2c_ctx *ctx; ++ ++ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); ++ if (!ctx) ++ return -ENOMEM; ++ ctx->adapter = inode->i_private; ++ filp->private_data = ctx; ++ return 0; ++} ++ ++static int ixgbe_dbg_sff_i2c_release(struct inode *inode, struct file *filp) ++{ ++ kfree(filp->private_data); ++ return 0; ++} ++ ++static ssize_t ixgbe_dbg_sff_i2c_write(struct file *filp, ++ const char __user *ubuf, ++ size_t count, loff_t *ppos) ++{ ++ struct ixgbe_sff_i2c_ctx *ctx = filp->private_data; ++ struct ixgbe_hw *hw = &ctx->adapter->hw; ++ u8 data[IXGBE_SFF_I2C_MAX]; ++ u8 wdata[IXGBE_SFF_I2C_MAX]; ++ char cmd[160]; ++ unsigned int addr, raddr, delay, n, wn, v; ++ int status, off, consumed, i; ++ char op; ++ ++ if (*ppos != 0 || count >= sizeof(cmd)) ++ return -EINVAL; ++ if (copy_from_user(cmd, ubuf, count)) ++ return -EFAULT; ++ cmd[count] = '\0'; ++ ++ if (sscanf(cmd, " %c %x %n", &op, &addr, &off) < 2) ++ return -EINVAL; ++ ++ if (op == 'r') { ++ if (sscanf(cmd + off, "%x", &n) != 1 || n < 1 || ++ n > IXGBE_SFF_I2C_MAX) ++ return -EINVAL; ++ status = ixgbe_i2c_raw_read(hw, addr, data, n); ++ } else if (op == 'w') { ++ n = 0; ++ while (n < IXGBE_SFF_I2C_MAX && ++ sscanf(cmd + off, "%x%n", &v, &consumed) == 1) { ++ data[n++] = v; ++ off += consumed; ++ } ++ status = ixgbe_i2c_raw_write(hw, addr, data, n); ++ } else if (op == 'x') { ++ if (sscanf(cmd + off, "%x %u %x %n", &raddr, &delay, &n, ++ &consumed) < 3) ++ return -EINVAL; ++ off += consumed; ++ if (n > IXGBE_SFF_I2C_MAX || delay > 50000) ++ return -EINVAL; ++ wn = 0; ++ while (wn < IXGBE_SFF_I2C_MAX && ++ sscanf(cmd + off, "%x%n", &v, &consumed) == 1) { ++ wdata[wn++] = v; ++ off += consumed; ++ } ++ status = ixgbe_i2c_raw_xfer(hw, addr, wdata, wn, delay, ++ raddr, data, n); ++ } else { ++ return -EINVAL; ++ } ++ ++ if (status) { ++ ctx->resp_len = scnprintf(ctx->resp, sizeof(ctx->resp), ++ "err %d\n", status); ++ } else if (op == 'r' || (op == 'x' && n)) { ++ off = scnprintf(ctx->resp, sizeof(ctx->resp), "ok"); ++ for (i = 0; i < n; i++) ++ off += scnprintf(ctx->resp + off, ++ sizeof(ctx->resp) - off, ++ " %02x", data[i]); ++ off += scnprintf(ctx->resp + off, sizeof(ctx->resp) - off, ++ "\n"); ++ ctx->resp_len = off; ++ } else { ++ ctx->resp_len = scnprintf(ctx->resp, sizeof(ctx->resp), "ok\n"); ++ } ++ ++ *ppos = 0; ++ return count; ++} ++ ++static ssize_t ixgbe_dbg_sff_i2c_read(struct file *filp, char __user *ubuf, ++ size_t count, loff_t *ppos) ++{ ++ struct ixgbe_sff_i2c_ctx *ctx = filp->private_data; ++ ++ return simple_read_from_buffer(ubuf, count, ppos, ctx->resp, ++ ctx->resp_len); ++} ++ ++static const struct file_operations ixgbe_dbg_sff_i2c_fops = { ++ .owner = THIS_MODULE, ++ .open = ixgbe_dbg_sff_i2c_open, ++ .release = ixgbe_dbg_sff_i2c_release, ++ .read = ixgbe_dbg_sff_i2c_read, ++ .write = ixgbe_dbg_sff_i2c_write, ++}; ++ + /** + * ixgbe_dbg_adapter_init - setup the debugfs directory for the adapter + * @adapter: the adapter that is starting up +@@ -198,6 +316,8 @@ void ixgbe_dbg_adapter_init(struct ixgbe_adapter *adapter) + adapter, &ixgbe_dbg_reg_ops_fops); + debugfs_create_file("netdev_ops", 0600, adapter->ixgbe_dbg_adapter, + adapter, &ixgbe_dbg_netdev_ops_fops); ++ debugfs_create_file("sff_i2c", 0600, adapter->ixgbe_dbg_adapter, ++ adapter, &ixgbe_dbg_sff_i2c_fops); + } + + /** +diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c +index 36e43b5e88d1..8bcb0ec0e65d 100644 +--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c ++++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c +@@ -2664,6 +2664,142 @@ static int ixgbe_set_coalesce(struct net_device *netdev, + return 0; + } + ++static u32 ixgbe_etqf_filter_count(struct ixgbe_adapter *adapter) ++{ ++ u32 n = 0; ++ int i; ++ ++ for (i = 0; i < IXGBE_MAX_ETQF_FILTERS; i++) ++ if (adapter->etqf_filters[i].used) ++ n++; ++ return n; ++} ++ ++static int ixgbe_add_etqf_entry(struct ixgbe_adapter *adapter, ++ struct ethtool_rxnfc *cmd) ++{ ++ struct ethtool_rx_flow_spec *fsp = ++ (struct ethtool_rx_flow_spec *)&cmd->fs; ++ static const u8 zero_mac[ETH_ALEN]; ++ struct ixgbe_etqf_entry *slot = NULL; ++ struct ixgbe_hw *hw = &adapter->hw; ++ u16 etype = ntohs(fsp->h_u.ether_spec.h_proto); ++ u32 ring; ++ int i; ++ ++ if (fsp->flow_type != ETHER_FLOW) ++ return -EINVAL; ++ if (fsp->m_u.ether_spec.h_proto != htons(0xFFFF) || ++ !ether_addr_equal(fsp->m_u.ether_spec.h_dest, zero_mac) || ++ !ether_addr_equal(fsp->m_u.ether_spec.h_source, zero_mac)) ++ return -EINVAL; ++ if (etype == ETH_P_1588) ++ return -EINVAL; ++ if (fsp->ring_cookie == RX_CLS_FLOW_DISC) ++ return -EINVAL; ++ ring = ethtool_get_flow_spec_ring(fsp->ring_cookie); ++ if (ethtool_get_flow_spec_ring_vf(fsp->ring_cookie) || ++ ring >= adapter->num_rx_queues) ++ return -EINVAL; ++ if (fsp->location >= ((1024 << adapter->fdir_pballoc) - 2)) ++ return -EINVAL; ++ ++ spin_lock(&adapter->fdir_perfect_lock); ++ for (i = 0; i < IXGBE_MAX_ETQF_FILTERS; i++) { ++ struct ixgbe_etqf_entry *e = &adapter->etqf_filters[i]; ++ ++ if (!e->used) ++ continue; ++ if (e->etype == etype && e->loc != fsp->location) { ++ spin_unlock(&adapter->fdir_perfect_lock); ++ return -EEXIST; ++ } ++ if (e->loc == fsp->location) ++ slot = e; ++ } ++ if (!slot) { ++ for (i = 0; i < IXGBE_MAX_ETQF_FILTERS; i++) { ++ if (i == IXGBE_ETQF_FILTER_1588 || ++ adapter->etqf_filters[i].used || ++ (IXGBE_READ_REG(hw, IXGBE_ETQF(i)) & ++ IXGBE_ETQF_FILTER_EN)) ++ continue; ++ slot = &adapter->etqf_filters[i]; ++ break; ++ } ++ } ++ if (!slot) { ++ spin_unlock(&adapter->fdir_perfect_lock); ++ return -ENOSPC; ++ } ++ ++ i = slot - adapter->etqf_filters; ++ slot->used = true; ++ slot->loc = fsp->location; ++ slot->etype = etype; ++ slot->ring = ring; ++ IXGBE_WRITE_REG(hw, IXGBE_ETQS(i), IXGBE_ETQS_QUEUE_EN | ++ ((u32)adapter->rx_ring[ring]->reg_idx << ++ IXGBE_ETQS_RX_QUEUE_SHIFT)); ++ IXGBE_WRITE_REG(hw, IXGBE_ETQF(i), IXGBE_ETQF_FILTER_EN | etype); ++ spin_unlock(&adapter->fdir_perfect_lock); ++ ++ return 0; ++} ++ ++/* Caller holds fdir_perfect_lock. */ ++static int ixgbe_del_etqf_entry(struct ixgbe_adapter *adapter, u32 loc) ++{ ++ struct ixgbe_hw *hw = &adapter->hw; ++ int i; ++ ++ for (i = 0; i < IXGBE_MAX_ETQF_FILTERS; i++) { ++ struct ixgbe_etqf_entry *e = &adapter->etqf_filters[i]; ++ ++ if (!e->used || e->loc != loc) ++ continue; ++ IXGBE_WRITE_REG(hw, IXGBE_ETQF(i), 0); ++ IXGBE_WRITE_REG(hw, IXGBE_ETQS(i), 0); ++ e->used = false; ++ return 0; ++ } ++ return -ENOENT; ++} ++ ++void ixgbe_etqf_filter_restore(struct ixgbe_adapter *adapter) ++{ ++ struct ixgbe_hw *hw = &adapter->hw; ++ int i; ++ ++ spin_lock(&adapter->fdir_perfect_lock); ++ for (i = 0; i < IXGBE_MAX_ETQF_FILTERS; i++) { ++ struct ixgbe_etqf_entry *e = &adapter->etqf_filters[i]; ++ ++ if (!e->used) ++ continue; ++ if (e->ring >= adapter->num_rx_queues) { ++ e->used = false; ++ continue; ++ } ++ IXGBE_WRITE_REG(hw, IXGBE_ETQS(i), IXGBE_ETQS_QUEUE_EN | ++ ((u32)adapter->rx_ring[e->ring]->reg_idx << ++ IXGBE_ETQS_RX_QUEUE_SHIFT)); ++ IXGBE_WRITE_REG(hw, IXGBE_ETQF(i), ++ IXGBE_ETQF_FILTER_EN | e->etype); ++ } ++ spin_unlock(&adapter->fdir_perfect_lock); ++} ++ ++void ixgbe_etqf_filter_exit(struct ixgbe_adapter *adapter) ++{ ++ int i; ++ ++ spin_lock(&adapter->fdir_perfect_lock); ++ for (i = 0; i < IXGBE_MAX_ETQF_FILTERS; i++) ++ adapter->etqf_filters[i].used = false; ++ spin_unlock(&adapter->fdir_perfect_lock); ++} ++ + static int ixgbe_get_ethtool_fdir_entry(struct ixgbe_adapter *adapter, + struct ethtool_rxnfc *cmd) + { +@@ -2672,10 +2808,23 @@ static int ixgbe_get_ethtool_fdir_entry(struct ixgbe_adapter *adapter, + (struct ethtool_rx_flow_spec *)&cmd->fs; + struct hlist_node *node2; + struct ixgbe_fdir_filter *rule = NULL; ++ int i; + + /* report total rule count */ + cmd->data = (1024 << adapter->fdir_pballoc) - 2; + ++ for (i = 0; i < IXGBE_MAX_ETQF_FILTERS; i++) { ++ struct ixgbe_etqf_entry *e = &adapter->etqf_filters[i]; ++ ++ if (!e->used || e->loc != fsp->location) ++ continue; ++ fsp->flow_type = ETHER_FLOW; ++ fsp->h_u.ether_spec.h_proto = htons(e->etype); ++ fsp->m_u.ether_spec.h_proto = htons(0xFFFF); ++ fsp->ring_cookie = e->ring; ++ return 0; ++ } ++ + hlist_for_each_entry_safe(rule, node2, + &adapter->fdir_filter_list, fdir_node) { + if (fsp->location <= rule->sw_idx) +@@ -2740,6 +2889,7 @@ static int ixgbe_get_ethtool_fdir_all(struct ixgbe_adapter *adapter, + struct hlist_node *node2; + struct ixgbe_fdir_filter *rule; + int cnt = 0; ++ int i; + + /* report total rule count */ + cmd->data = (1024 << adapter->fdir_pballoc) - 2; +@@ -2752,6 +2902,17 @@ static int ixgbe_get_ethtool_fdir_all(struct ixgbe_adapter *adapter, + cnt++; + } + ++ for (i = 0; i < IXGBE_MAX_ETQF_FILTERS; i++) { ++ struct ixgbe_etqf_entry *e = &adapter->etqf_filters[i]; ++ ++ if (!e->used) ++ continue; ++ if (cnt == cmd->rule_cnt) ++ return -EMSGSIZE; ++ rule_locs[cnt] = e->loc; ++ cnt++; ++ } ++ + cmd->rule_cnt = cnt; + + return 0; +@@ -2825,7 +2986,9 @@ static int ixgbe_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd, + + switch (cmd->cmd) { + case ETHTOOL_GRXCLSRLCNT: +- cmd->rule_cnt = adapter->fdir_filter_count; ++ cmd->rule_cnt = adapter->fdir_filter_count + ++ ixgbe_etqf_filter_count(adapter); ++ cmd->data = (1024 << adapter->fdir_pballoc) - 2; + ret = 0; + break; + case ETHTOOL_GRXCLSRULE: +@@ -3077,7 +3240,10 @@ static int ixgbe_del_ethtool_fdir_entry(struct ixgbe_adapter *adapter, + int err; + + spin_lock(&adapter->fdir_perfect_lock); +- err = ixgbe_update_ethtool_fdir_entry(adapter, NULL, fsp->location); ++ err = ixgbe_del_etqf_entry(adapter, fsp->location); ++ if (err) ++ err = ixgbe_update_ethtool_fdir_entry(adapter, NULL, ++ fsp->location); + spin_unlock(&adapter->fdir_perfect_lock); + + return err; +@@ -3207,7 +3373,10 @@ static int ixgbe_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd) + + switch (cmd->cmd) { + case ETHTOOL_SRXCLSRLINS: +- ret = ixgbe_add_ethtool_fdir_entry(adapter, cmd); ++ if ((cmd->fs.flow_type & ~FLOW_EXT) == ETHER_FLOW) ++ ret = ixgbe_add_etqf_entry(adapter, cmd); ++ else ++ ret = ixgbe_add_ethtool_fdir_entry(adapter, cmd); + break; + case ETHTOOL_SRXCLSRLDEL: + ret = ixgbe_del_ethtool_fdir_entry(adapter, cmd); +diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_fcoe.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_fcoe.c +index 011fda9c6193..1b5cb4f7bb68 100644 +--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_fcoe.c ++++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_fcoe.c +@@ -647,6 +647,10 @@ void ixgbe_configure_fcoe(struct ixgbe_adapter *adapter) + if (!(adapter->netdev->features & NETIF_F_FCOE_CRC)) + return; + ++ /* leave registers un-configured if FCoE is disabled */ ++ if (!(adapter->flags & IXGBE_FLAG_FCOE_ENABLED)) ++ return; ++ + /* Enable L2 EtherType filter for FCoE, needed for FCoE CRC and DDP */ + etqf = ETH_P_FCOE | IXGBE_ETQF_FCOE | IXGBE_ETQF_FILTER_EN; + if (adapter->flags & IXGBE_FLAG_SRIOV_ENABLED) { +@@ -656,10 +660,6 @@ void ixgbe_configure_fcoe(struct ixgbe_adapter *adapter) + IXGBE_WRITE_REG(hw, IXGBE_ETQF(IXGBE_ETQF_FILTER_FCOE), etqf); + IXGBE_WRITE_REG(hw, IXGBE_ETQS(IXGBE_ETQF_FILTER_FCOE), 0); + +- /* leave registers un-configured if FCoE is disabled */ +- if (!(adapter->flags & IXGBE_FLAG_FCOE_ENABLED)) +- return; +- + /* Use one or more Rx queues for FCoE by redirection table */ + fcreta_size = IXGBE_FCRETA_SIZE; + if (adapter->hw.mac.type == ixgbe_mac_X550) +diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c +index 8873a8cc4a18..85fdf1ab4177 100644 +--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c ++++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c +@@ -5911,6 +5911,8 @@ static void ixgbe_configure(struct ixgbe_adapter *adapter) + ixgbe_fdir_filter_restore(adapter); + } + ++ ixgbe_etqf_filter_restore(adapter); ++ + switch (hw->mac.type) { + case ixgbe_mac_82599EB: + case ixgbe_mac_X540: +@@ -7533,6 +7535,7 @@ int ixgbe_close(struct net_device *netdev) + ixgbe_close_suspend(adapter); + + ixgbe_fdir_filter_exit(adapter); ++ ixgbe_etqf_filter_exit(adapter); + + ixgbe_release_hw_control(adapter); + +diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.c +index ab733e73927d..fb876b9e9395 100644 +--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.c ++++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.c +@@ -60,6 +60,102 @@ static int ixgbe_in_i2c_byte_ack(struct ixgbe_hw *hw, u8 *byte) + return ixgbe_clock_out_i2c_bit(hw, false); + } + ++static int ixgbe_i2c_raw_write_unlocked(struct ixgbe_hw *hw, u8 addr, ++ const u8 *buf, u8 len) ++{ ++ int status; ++ u8 i; ++ ++ ixgbe_i2c_start(hw); ++ status = ixgbe_out_i2c_byte_ack(hw, addr); ++ for (i = 0; !status && i < len; i++) ++ status = ixgbe_out_i2c_byte_ack(hw, buf[i]); ++ ++ if (status) ++ ixgbe_i2c_bus_clear(hw); ++ else ++ ixgbe_i2c_stop(hw); ++ ++ return status; ++} ++ ++static int ixgbe_i2c_raw_read_unlocked(struct ixgbe_hw *hw, u8 addr, ++ u8 *buf, u8 len) ++{ ++ u32 i2cctl; ++ int status; ++ u8 i; ++ ++ ixgbe_i2c_start(hw); ++ status = ixgbe_out_i2c_byte_ack(hw, addr); ++ for (i = 0; !status && i < len; i++) { ++ status = ixgbe_clock_in_i2c_byte(hw, &buf[i]); ++ if (!status) ++ status = ixgbe_clock_out_i2c_bit(hw, i == len - 1); ++ if (!status && i != len - 1) { ++ /* our ACK left SDA driven low; release it so the ++ * slave can drive the next byte ++ */ ++ i2cctl = IXGBE_READ_REG(hw, IXGBE_I2CCTL(hw)); ++ i2cctl |= IXGBE_I2C_DATA_OUT(hw); ++ i2cctl |= IXGBE_I2C_DATA_OE_N_EN(hw); ++ IXGBE_WRITE_REG(hw, IXGBE_I2CCTL(hw), i2cctl); ++ IXGBE_WRITE_FLUSH(hw); ++ } ++ } ++ ++ if (status) ++ ixgbe_i2c_bus_clear(hw); ++ else ++ ixgbe_i2c_stop(hw); ++ ++ return status; ++} ++ ++int ixgbe_i2c_raw_write(struct ixgbe_hw *hw, u8 addr, const u8 *buf, u8 len) ++{ ++ u32 swfw_mask = hw->phy.phy_semaphore_mask; ++ int status; ++ ++ if (hw->mac.ops.acquire_swfw_sync(hw, swfw_mask)) ++ return -EBUSY; ++ status = ixgbe_i2c_raw_write_unlocked(hw, addr, buf, len); ++ hw->mac.ops.release_swfw_sync(hw, swfw_mask); ++ return status; ++} ++ ++int ixgbe_i2c_raw_read(struct ixgbe_hw *hw, u8 addr, u8 *buf, u8 len) ++{ ++ u32 swfw_mask = hw->phy.phy_semaphore_mask; ++ int status; ++ ++ if (hw->mac.ops.acquire_swfw_sync(hw, swfw_mask)) ++ return -EBUSY; ++ status = ixgbe_i2c_raw_read_unlocked(hw, addr, buf, len); ++ hw->mac.ops.release_swfw_sync(hw, swfw_mask); ++ return status; ++} ++ ++int ixgbe_i2c_raw_xfer(struct ixgbe_hw *hw, u8 waddr, const u8 *wbuf, u8 wlen, ++ u32 delay_us, u8 raddr, u8 *rbuf, u8 rlen) ++{ ++ u32 swfw_mask = hw->phy.phy_semaphore_mask; ++ int status = 0; ++ ++ if (hw->mac.ops.acquire_swfw_sync(hw, swfw_mask)) ++ return -EBUSY; ++ ++ if (wlen) ++ status = ixgbe_i2c_raw_write_unlocked(hw, waddr, wbuf, wlen); ++ if (!status && delay_us) ++ usleep_range(delay_us, delay_us + delay_us / 10 + 10); ++ if (!status && rlen) ++ status = ixgbe_i2c_raw_read_unlocked(hw, raddr, rbuf, rlen); ++ ++ hw->mac.ops.release_swfw_sync(hw, swfw_mask); ++ return status; ++} ++ + /** + * ixgbe_ones_comp_byte_add - Perform one's complement addition + * @add1: addend 1 +diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.h b/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.h +index 039ba4b6c120..b93bdd972d7a 100644 +--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.h ++++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.h +@@ -178,4 +178,8 @@ int ixgbe_read_i2c_combined_generic_int(struct ixgbe_hw *, u8 addr, u16 reg, + u16 *val, bool lock); + int ixgbe_write_i2c_combined_generic_int(struct ixgbe_hw *, u8 addr, u16 reg, + u16 val, bool lock); ++int ixgbe_i2c_raw_write(struct ixgbe_hw *hw, u8 addr, const u8 *buf, u8 len); ++int ixgbe_i2c_raw_read(struct ixgbe_hw *hw, u8 addr, u8 *buf, u8 len); ++int ixgbe_i2c_raw_xfer(struct ixgbe_hw *hw, u8 waddr, const u8 *wbuf, u8 wlen, ++ u32 delay_us, u8 raddr, u8 *rbuf, u8 rlen); + #endif /* _IXGBE_PHY_H_ */ diff --git a/load-ixgbe b/load-ixgbe new file mode 100755 index 0000000..612f494 --- /dev/null +++ b/load-ixgbe @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Dev-host module swap: builds the bench ixgbe (the product patch plus the +# bench-only extras) out of tree and replaces the running driver. Does not +# survive a reboot; the appliance image gets the product patch from +# kernel/ixgbe-sff.patch via mkimage instead. +src=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +src=$(dirname "$src")/ixgbe-sff +make -C "/usr/lib/modules/$(uname -r)/build" "M=$src" modules +sudo rmmod ixgbe +sudo insmod "$src/ixgbe.ko" allow_unsupported_sfp=1 +sudo sh -c 'ls /sys/kernel/debug/ixgbe/*/sff_i2c' diff --git a/main.go b/main.go index 5c6b8c9..06331ff 100644 --- a/main.go +++ b/main.go @@ -207,12 +207,15 @@ func run(aName, bName string) (err error) { ethertypes[i] = uint16(etherBase + i) } + // The modules are only reachable behind an up port: ixgbe holds + // TX_DISABLE asserted while the netdev is admin-down and the module + // answers every PHY read with 0xffff (proven by flipping admin state on + // the bench). The host checks raise the links, so they run first. + checks := configureSystem(ifnames, ethertypes) modules, moduleIDs, err := openModules([2]string{a.name, b.name}) if err != nil { return err } - - checks := configureSystem(ifnames, ethertypes) checks = append(checks, moduleChecks(modules, [2]string{a.name, b.name})...) if err := reportChecks("SETTINGS", checks); err != nil { return err diff --git a/mkimage b/mkimage index 5a72762..0143987 100755 --- a/mkimage +++ b/mkimage @@ -25,26 +25,25 @@ keys=( # distro's linux-firmware. edid=edid/waveshare-1024x600.bin distrofw=( - intel/ice/ddp/ice.pkg i915/adlp_dmc.bin i915/adlp_guc_70.bin i915/tgl_huc.bin ) -cmdline="drm.edid_firmware=HDMI-A-1:$edid video=HDMI-A-1:e" +cmdline="drm.edid_firmware=HDMI-A-1:$edid video=HDMI-A-1:e ixgbe.allow_unsupported_sfp=1" # Every setting the appliance depends on, re-asserted after the stored config # is loaded so a renamed or newly defaulted symbol cannot quietly drop one. wanty=( EFI_STUB BLK_DEV_INITRD INITRAMFS_COMPRESSION_NONE CMDLINE_BOOL PACKET INET DEVTMPFS PROC_FS SYSFS TMPFS - ICE ICE_HWTS I40E PTP_1588_CLOCK + IXGBE DEBUG_FS PTP_1588_CLOCK DRM_I915 DRM_FBDEV_EMULATION DRM_LOAD_EDID_FIRMWARE DRM_PANIC FB_DEVICE FRAMEBUFFER_CONSOLE VT_CONSOLE USB_XHCI_PCI USB_HID HID_MULTITOUCH INPUT_EVDEV CPU_FREQ_GOV_PERFORMANCE X86_INTEL_PSTATE ) -wantn=(MODULES BLOCK IGC) +wantn=(MODULES BLOCK IGC ICE I40E) say() { printf '\n\033[36m== %s\033[0m\n' "$*"; } die() { printf '\033[31merror: %s\033[0m\n' "$*" >&2; exit 1; } @@ -71,7 +70,14 @@ done [ -n "$signer" ] || die "$tag is not signed by a pinned kernel.org key" echo "$tag verified against $signer" -git -C "$src" checkout -q --detach "$tag" +# -f discards the previous run's applied patch: the tree is a build +# workspace, and kernel/ixgbe-sff.patch is the source of truth. +git -C "$src" checkout -f -q --detach "$tag" + +say "ixgbe patch" +git -C "$src" apply --stat "$repo/kernel/ixgbe-sff.patch" +git -C "$src" apply "$repo/kernel/ixgbe-sff.patch" || + die "kernel/ixgbe-sff.patch does not apply to $tag; port the patch first" say "go binary" mkdir -p "$initdir" @@ -111,7 +117,7 @@ cp "$repo/kernel/config" "$src/.config" ( cd "$src" && scripts/config \ --disable MODULES \ --enable EXPERT --disable BLOCK \ - --enable I40E --disable IGC \ + --enable IXGBE --disable ICE --disable I40E --disable IGC \ --enable DRM_LOAD_EDID_FIRMWARE \ --set-str EXTRA_FIRMWARE "$edid ${distrofw[*]}" \ --set-str EXTRA_FIRMWARE_DIR "$fwdir" \ diff --git a/phy.go b/phy.go index 8e26f24..76f5eef 100644 --- a/phy.go +++ b/phy.go @@ -102,6 +102,11 @@ const ( linkWaitSpan = 25 * time.Second linkWaitPoll = time.Second + // The PHY core wakes within 0.25 s of the port raise (bench-measured: + // BCM instant, Wiitek 0.07-0.24 s across cycles). + moduleUpWait = 3 * time.Second + moduleUpPoll = 100 * time.Millisecond + snrGoodMargin = 3.0 snrWarnMargin = 1.0 ) @@ -493,9 +498,20 @@ func openModules(names [2]string) ([]*phyModule, [2]string, error) { default: return nil, idents, fmt.Errorf("%s: unknown module PN %q", name, pn) } - idents[i], err = dev.identify() - if err != nil { - return nil, idents, err + // The port was just raised (the host checks run first): TX_DISABLE + // deasserts and the module re-powers its PHY core, which answers + // 0xffff through a working mailbox until it is back. Identify + // settles bounded while the core wakes. + deadline := time.Now().Add(moduleUpWait) + for { + idents[i], err = dev.identify() + if err == nil { + break + } + if time.Now().After(deadline) { + return nil, idents, err + } + time.Sleep(moduleUpPoll) } m := &phyModule{dev: dev} // Born busy: the pollers stay silent through bringup's SETs and