package main import ( "fmt" "os" "path/filepath" "strings" "golang.org/x/sys/unix" ) type sff struct { ifname string path string // Every transport touch happens on the loop goroutine: requests execute // one at a time, each admitted by the module type's own gate first. reqs chan func() admit func() } func openSFF(ifname string) (*sff, error) { devLink, err := os.Readlink("/sys/class/net/" + ifname + "/device") if err != nil { return nil, fmt.Errorf("%s: %w", ifname, err) } drv, err := ifDriver(ifname) if err != nil { return nil, fmt.Errorf("%s: %w", ifname, err) } if drv != "ixgbe" { return nil, fmt.Errorf("%s: no module I2C transport for driver %s", ifname, drv) } s := &sff{ ifname: ifname, path: "/sys/kernel/debug/ixgbe/" + filepath.Base(devLink) + "/sff_i2c", reqs: make(chan func()), admit: func() {}, } if _, err := os.Stat(s.path); err != nil { return nil, fmt.Errorf("%s: %w (patched ixgbe?)", ifname, err) } go s.loop() return s, nil } func (s *sff) loop() { defer holdPanic() for fn := range s.reqs { s.admit() fn() } } func (s *sff) exec(fn func()) { done := make(chan struct{}) s.reqs <- func() { fn(); close(done) } <-done } func (s *sff) name() string { return s.ifname } func (s *sff) op(cmd string) (string, error) { fd, err := unix.Open(s.path, unix.O_RDWR, 0) if err != nil { return "", fmt.Errorf("%s: %w", s.path, err) } defer unix.Close(fd) if _, err := unix.Write(fd, []byte(cmd)); err != nil { return "", fmt.Errorf("%s %q: %w", s.ifname, cmd, err) } buf := make([]byte, 256) n, err := unix.Read(fd, buf) if err != nil { return "", fmt.Errorf("%s %q: %w", s.ifname, cmd, err) } resp := strings.TrimSpace(string(buf[:n])) if !strings.HasPrefix(resp, "ok") { return "", fmt.Errorf("%s %q: %s", s.ifname, cmd, resp) } return strings.TrimSpace(resp[2:]), nil } func parseHexBytes(s string, n int) ([]byte, error) { fields := strings.Fields(s) if len(fields) != n { return nil, fmt.Errorf("want %d bytes, got %q", n, s) } out := make([]byte, n) for i, f := range fields { var v byte if _, err := fmt.Sscanf(f, "%x", &v); err != nil { return nil, fmt.Errorf("byte %q in %q", f, s) } out[i] = v } return out, nil } // A single bus hold; split write/read ops would let the driver's own SFP // traffic consume the bridge's pending read. func (s *sff) compound(waddr, raddr byte, delayUs, n int, wdata []byte) ([]byte, error) { var sb strings.Builder fmt.Fprintf(&sb, "x %02x %02x %d %x", waddr, raddr, delayUs, n) for _, v := range wdata { fmt.Fprintf(&sb, " %02x", v) } resp, err := s.op(sb.String()) if err != nil { return nil, err } return parseHexBytes(resp, n) } func (s *sff) eeprom(off byte, n int) ([]byte, error) { return s.compound(0xA0, 0xA1, 500, n, []byte{off}) } func (s *sff) vendorPN() (string, error) { pn, err := s.eeprom(40, 16) if err != nil { return "", err } return strings.TrimSpace(string(pn)), nil }