126 lines
2.9 KiB
Go
126 lines
2.9 KiB
Go
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
|
||
|
|
}
|