Drive a noise cable on the i40e pair, cycling its link down and up since the module PHYs ignore everything softer, and flag its absence beside the error columns

This commit is contained in:
flamingcow
2026-08-05 10:49:23 -07:00
parent 88f4f7da80
commit 2fcab83756
5 changed files with 335 additions and 32 deletions
+72 -10
View File
@@ -334,8 +334,9 @@ func getCoalesce(fd int, ifname string) (ethtoolCoalesce, error) {
}
const (
ethSSStats = 1
ethGstringLen = 32
ethSSStats = 1
ethSSPrivFlags = 2
ethGstringLen = 32
)
// Each of these has a __u32 or __u8 tail the kernel fills past the end of the
@@ -364,33 +365,33 @@ type ethtoolStatsHdr struct {
// boundary. A []byte carries no such guarantee.
func statsBuf(n int) []uint64 { return make([]uint64, n) }
func statCount(fd int, ifname string) (uint32, error) {
req := ethtoolSsetInfo{cmd: unix.ETHTOOL_GSSET_INFO, ssetMask: 1 << ethSSStats}
func stringSetCount(fd int, ifname string, set uint32) (uint32, error) {
req := ethtoolSsetInfo{cmd: unix.ETHTOOL_GSSET_INFO, ssetMask: 1 << set}
if err := ethtoolCall(fd, ifname, unsafe.Pointer(&req)); err != nil {
return 0, err
}
// A cleared mask bit means the driver does not have that string set at all,
// in which case no count was written into the tail.
if req.ssetMask == 0 {
return 0, fmt.Errorf("driver has no ETH_SS_STATS string set")
return 0, fmt.Errorf("driver has no string set %d", set)
}
return req.data[0], nil
}
func statNames(fd int, ifname string) ([]string, error) {
n, err := statCount(fd, ifname)
func stringSetNames(fd int, ifname string, set uint32) ([]string, error) {
n, err := stringSetCount(fd, ifname, set)
if err != nil {
return nil, err
}
if n == 0 {
return nil, fmt.Errorf("driver reports zero statistics")
return nil, fmt.Errorf("driver reports an empty string set %d", set)
}
hdrLen := int(unsafe.Sizeof(ethtoolGstrings{}))
buf := statsBuf((hdrLen + int(n)*ethGstringLen + 7) / 8)
hdr := (*ethtoolGstrings)(unsafe.Pointer(&buf[0]))
hdr.cmd = unix.ETHTOOL_GSTRINGS
hdr.stringSet = ethSSStats
hdr.stringSet = set
hdr.len = n
if err := ethtoolCall(fd, ifname, unsafe.Pointer(&buf[0])); err != nil {
return nil, err
@@ -426,7 +427,7 @@ type statReader struct {
}
func newStatReader(fd int, ifname string, want []string) (*statReader, error) {
names, err := statNames(fd, ifname)
names, err := stringSetNames(fd, ifname, ethSSStats)
if err != nil {
return nil, fmt.Errorf("%s statistics: %w", ifname, err)
}
@@ -623,6 +624,67 @@ func withIoctlSocket(fn func(fd int) []checkResult) []checkResult {
return fn(fd)
}
type ethtoolValue struct {
cmd uint32
data uint32
}
// i40e leaves the module transmitting when a port is closed, so the peer never
// sees anything happen and the copper stays trained. This flag is what makes
// an administrative down reach the wire, and the noise cycle is switched
// entirely through it.
func checkPrivFlag(fd int, ifname, flag string) checkResult {
res := checkResult{item: ifname + " " + flag}
names, err := stringSetNames(fd, ifname, ethSSPrivFlags)
if err != nil {
res.err = err
return res
}
bit := -1
for i, s := range names {
if s == flag {
bit = i
break
}
}
if bit < 0 {
res.err = fmt.Errorf("driver has no private flag %q", flag)
return res
}
v := ethtoolValue{cmd: unix.ETHTOOL_GPFLAGS}
if err := ethtoolCall(fd, ifname, unsafe.Pointer(&v)); err != nil {
res.err = err
return res
}
if v.data&(1<<bit) != 0 {
res.state = "on"
return res
}
v.cmd = unix.ETHTOOL_SPFLAGS
v.data |= 1 << bit
if err := ethtoolCall(fd, ifname, unsafe.Pointer(&v)); err != nil {
res.err = err
res.state = "could not set"
return res
}
res.fixed = true
res.state = "was off, now on"
return res
}
// The noise ports get none of the test pair's tuning: nothing is measured on
// them, so rings, coalescing and steering are all beside the point. All they
// need is for a closed port to really drop the link.
func configureNoise(ifnames []string) []checkResult {
return withIoctlSocket(func(fd int) []checkResult {
var out []checkResult
for _, ifname := range ifnames {
out = append(out, checkPrivFlag(fd, ifname, "link-down-on-close"))
}
return out
})
}
// Nothing here waits for a carrier: the two ports are the two ends of the cable
// under test, so with no cable there is never going to be one.
func configureSystem(ifnames []string, ethertypes []uint16) []checkResult {