From b603948a61be56b4b7ad79f4a49ac3f107da971f Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 3 Jun 2026 13:31:07 +0200 Subject: [PATCH 01/36] power mode flag --- cmd/optic-programmer.go | 4 +++ internal/pkg/routines/routines.go | 11 +++++-- internal/pkg/routines/write_actions.go | 44 ++++++++++++++++++++------ internal/pkg/rtbrick/i2c.go | 7 ++-- 4 files changed, 53 insertions(+), 13 deletions(-) diff --git a/cmd/optic-programmer.go b/cmd/optic-programmer.go index 63d1bfd..2d3a8c2 100644 --- a/cmd/optic-programmer.go +++ b/cmd/optic-programmer.go @@ -71,6 +71,10 @@ func main() { &cli.IntFlag{ Name: "channel", }, + &cli.StringFlag{ + Name: "power", + Value: "low", + }, }, Action: routines.I2CWriteAll, }, diff --git a/internal/pkg/routines/routines.go b/internal/pkg/routines/routines.go index 4869521..af162f1 100644 --- a/internal/pkg/routines/routines.go +++ b/internal/pkg/routines/routines.go @@ -8,6 +8,11 @@ import ( connection "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/ssh" ) +var CmdStringToPowerMode = map[string]rtbrick.PowerMode{ + "high": rtbrick.PowerModeHighPower, + "low": rtbrick.PowerModeLowPower, +} + type I2cRWHandle struct { Connection *connection.RouterConnection I2cBusId int @@ -120,12 +125,14 @@ var I2CReadAll = I2cTemplateMethod(i2cReadActions[:]) var i2cWriteActions = [...]I2cAction{ ActionShowBasicAdminInfo, + // some optics require disabling high power before programming ActionSetPowerModeTo(rtbrick.PowerModeLowPower), - ActionEnablePowerClassOverride, + ActionShowFlexOptixCustomPages, ActionDisableFlexTune, ActionSetGridProgramming, ActionEnableNominalWavelengthControl, - ActionSetPowerModeTo(rtbrick.PowerModeHighPower), + ActionSetPowerClassOverride, + ActionSetPowerMode, } var I2CWriteAll = I2cTemplateMethod(i2cWriteActions[:]) diff --git a/internal/pkg/routines/write_actions.go b/internal/pkg/routines/write_actions.go index e1d4b44..f7aa00f 100644 --- a/internal/pkg/routines/write_actions.go +++ b/internal/pkg/routines/write_actions.go @@ -5,6 +5,7 @@ import ( "strconv" "time" + "github.com/pkg/errors" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick" ) @@ -28,19 +29,44 @@ func ActionSetPowerModeTo(power rtbrick.PowerMode) I2cAction { } } -func ActionEnablePowerClassOverride(args I2cActionArgs) error { - if args.Page1E.PowerClassOverride != 0x01 { - slog.Info("Setting Power Class Override...") +func ActionSetPowerMode(args I2cActionArgs) error { + powerMode, ok := CmdStringToPowerMode[args.Cmd.String("power")] + if !ok { + return errors.New("power mode does not exist") + } + err := ActionSetPowerModeTo(powerMode)(args) + if err != nil { + return err + } - wPage, wByte, wValue := rtbrick.GetPowerClassProgramming() - err := args.Handle.Connection.DoI2CSet(args.Handle.I2cBusId, wPage, wByte, wValue) - if err != nil { - return err - } + return nil +} - time.Sleep(1 * time.Second) +func ActionSetPowerClassOverride(args I2cActionArgs) error { + modeStr := args.Cmd.String("power") + powerMode, ok := CmdStringToPowerMode[modeStr] + if !ok { + return errors.New("power mode does not exist") } + wPage, wByte, wValue := rtbrick.GetPowerClassProgramming(false) // default low + if powerMode == rtbrick.PowerModeHighPower && args.Page1E.PowerClassOverride != 0x01 { + wPage, wByte, wValue = rtbrick.GetPowerClassProgramming(true) + } else if powerMode == rtbrick.PowerModeLowPower && args.Page1E.PowerClassOverride != 0x00 { + wPage, wByte, wValue = rtbrick.GetPowerClassProgramming(false) + } else { + slog.Info("power_class", slog.String("mode", "already_set")) + return nil + } + + slog.Info("power_class_set", slog.String("mode", modeStr)) + err := args.Handle.Connection.DoI2CSet(args.Handle.I2cBusId, wPage, wByte, wValue) + if err != nil { + return err + } + + time.Sleep(1 * time.Second) + return nil } diff --git a/internal/pkg/rtbrick/i2c.go b/internal/pkg/rtbrick/i2c.go index b8b358a..a3101f2 100644 --- a/internal/pkg/rtbrick/i2c.go +++ b/internal/pkg/rtbrick/i2c.go @@ -228,8 +228,11 @@ func GetFlexTuneProgramming() (page, byte int, value byte) { return 0x1E, 200, flexTuneBit } -func GetPowerClassProgramming() (page, byte int, value byte) { - var powerClassBit uint8 = 0x01 +func GetPowerClassProgramming(enable bool) (page, byte int, value byte) { + var powerClassBit uint8 = 0x00 + if enable { + powerClassBit = 0x01 + } return 0x1E, 253, powerClassBit } From e917040b0f3a18cfbcdb9ebc88bd51aedf3d885e Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Thu, 4 Jun 2026 11:26:03 +0200 Subject: [PATCH 02/36] new architecture skel --- internal/pkg/optic/cmis/cmis.go | 41 +++++ internal/pkg/optic/cmis/finisar.go | 1 + internal/pkg/optic/cmis/flexoptix.go | 1 + internal/pkg/optic/default.go | 50 ++++++ internal/pkg/{rtbrick => optic}/dwdm.go | 2 +- internal/pkg/{rtbrick => optic}/i2c.go | 12 +- internal/pkg/optic/sff8636/finisar.go | 1 + internal/pkg/optic/sff8636/flexoptix.go | 1 + internal/pkg/optic/sff8636/sff8636.go | 42 +++++ .../pkg/{rtbrick => optic/util}/bitmask.go | 2 +- internal/pkg/public.go | 145 ++++++++++++++++++ internal/pkg/routines/routines.go | 81 +++------- internal/pkg/routines/write_actions.go | 30 ++-- internal/pkg/{ => rtbrick}/ssh/connection.go | 4 +- internal/pkg/rtbrick/ssh/i2c_handle.go | 38 +++++ 15 files changed, 368 insertions(+), 83 deletions(-) create mode 100644 internal/pkg/optic/cmis/cmis.go create mode 100644 internal/pkg/optic/cmis/finisar.go create mode 100644 internal/pkg/optic/cmis/flexoptix.go create mode 100644 internal/pkg/optic/default.go rename internal/pkg/{rtbrick => optic}/dwdm.go (98%) rename internal/pkg/{rtbrick => optic}/i2c.go (96%) create mode 100644 internal/pkg/optic/sff8636/finisar.go create mode 100644 internal/pkg/optic/sff8636/flexoptix.go create mode 100644 internal/pkg/optic/sff8636/sff8636.go rename internal/pkg/{rtbrick => optic/util}/bitmask.go (95%) create mode 100644 internal/pkg/public.go rename internal/pkg/{ => rtbrick}/ssh/connection.go (98%) create mode 100644 internal/pkg/rtbrick/ssh/i2c_handle.go diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go new file mode 100644 index 0000000..38a78cb --- /dev/null +++ b/internal/pkg/optic/cmis/cmis.go @@ -0,0 +1,41 @@ +package cmis + +import ( + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" +) + +type State pkg.ModuleState + +func (s2 *State) Accepts(sff8024Identifier byte) bool { + return sff8024Identifier == 0x11 +} + +func (s2 *State) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { + //TODO implement me + panic("implement me") +} + +func (s2 *State) Get() (*pkg.ModuleState, error) { + //TODO implement me + panic("implement me") +} + +func (s2 *State) GetAdministrativeInformation() (*pkg.ModuleState, error) { + //TODO implement me + panic("implement me") +} + +func (s2 *State) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { + //TODO implement me + panic("implement me") +} + +func (s2 *State) GetTunableLaserCtrlStatus() (*pkg.ModuleState, error) { + //TODO implement me + panic("implement me") +} + +func (s2 *State) SetTunableLaserCtrlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { + //TODO implement me + panic("implement me") +} diff --git a/internal/pkg/optic/cmis/finisar.go b/internal/pkg/optic/cmis/finisar.go new file mode 100644 index 0000000..8480d15 --- /dev/null +++ b/internal/pkg/optic/cmis/finisar.go @@ -0,0 +1 @@ +package cmis diff --git a/internal/pkg/optic/cmis/flexoptix.go b/internal/pkg/optic/cmis/flexoptix.go new file mode 100644 index 0000000..8480d15 --- /dev/null +++ b/internal/pkg/optic/cmis/flexoptix.go @@ -0,0 +1 @@ +package cmis diff --git a/internal/pkg/optic/default.go b/internal/pkg/optic/default.go new file mode 100644 index 0000000..7a9c2be --- /dev/null +++ b/internal/pkg/optic/default.go @@ -0,0 +1,50 @@ +package optic + +import ( + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" +) + +// State is the default Management strategy which should be set at initialization +// it can only read page00, and does 0 write. It can tell which concrete strategy which you should +// be using by looking at identifier codes and constructor ASCII +type State pkg.ModuleState + +func (d *State) Accepts(_ byte) bool { + return true +} + +func (d *State) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { + //TODO implement me + panic(genericWriteErrorString) +} + +func (d *State) Get() (*pkg.ModuleState, error) { + //TODO implement me + panic(genericReadErrorString) +} + +func (d *State) GetAdministrativeInformation() (*pkg.ModuleState, error) { + // TODO + panic("implement me") +} + +func (d *State) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { + panic(genericWriteErrorString) +} + +func (d *State) GetTunableLaserCtrlStatus() (*pkg.ModuleState, error) { + panic(genericReadErrorString) +} + +func (d *State) SetTunableLaserCtrlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { + panic(genericWriteErrorString) +} + +var genericWriteErrorString = "Module type is unknown, therefore I cannot go on, " + + "and refuse to set any value as it might have undesired effect. " + + "Program will be terminated now, as it is likely I failed to detect the Module Type." + +var genericReadErrorString = "Module type is unknown, therefore I cannot go on, " + + "as you require me making sense of things I do not know the shape, " + + "nor give an absolute and complete answer, for I do not understand all that is before me. " + + "Program will be terminated now, as it is likely I failed to detect the Module Type." diff --git a/internal/pkg/rtbrick/dwdm.go b/internal/pkg/optic/dwdm.go similarity index 98% rename from internal/pkg/rtbrick/dwdm.go rename to internal/pkg/optic/dwdm.go index 3369f70..f6debe3 100644 --- a/internal/pkg/rtbrick/dwdm.go +++ b/internal/pkg/optic/dwdm.go @@ -1,4 +1,4 @@ -package rtbrick +package optic // This is taken from https://www.opternus.de/wissen/netzwerkprotokoll-messtechnik/dwdm-grid-saemtliche-dwdm-kanaele-mit-ihren-absoluten-werten-nach-frequenz-und-wellenlaenge diff --git a/internal/pkg/rtbrick/i2c.go b/internal/pkg/optic/i2c.go similarity index 96% rename from internal/pkg/rtbrick/i2c.go rename to internal/pkg/optic/i2c.go index a3101f2..7409ec6 100644 --- a/internal/pkg/rtbrick/i2c.go +++ b/internal/pkg/optic/i2c.go @@ -1,4 +1,4 @@ -package rtbrick +package optic import ( "bytes" @@ -8,9 +8,11 @@ import ( "math" "strconv" "strings" + + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" ) -func ParseI2CDump(dump string) ([]byte, error) { +func ParseI2CDump(dump string) []byte { slog.Debug("======== I2C Dump ========") slog.Debug("\n" + dump) slog.Debug("======== ======== ========") @@ -38,7 +40,7 @@ func ParseI2CDump(dump string) ([]byte, error) { allBytes := w.Bytes() slog.Debug("raw_decoded_i2c_bytes", slog.String("hex_string", hex.EncodeToString(allBytes))) - return allBytes, nil + return allBytes } @@ -267,9 +269,9 @@ type I2CPage00 struct { func InterpretPage00(dump []byte) I2CPage00 { - bit99Bitmask := Bitmask(dump[99]) + bit99Bitmask := util.Bitmask(dump[99]) - isLowPowerMode := bit99Bitmask.Has(Bit1) + isLowPowerMode := bit99Bitmask.Has(util.Bit1) return I2CPage00{ LowPowerMode: isLowPowerMode, diff --git a/internal/pkg/optic/sff8636/finisar.go b/internal/pkg/optic/sff8636/finisar.go new file mode 100644 index 0000000..c40351c --- /dev/null +++ b/internal/pkg/optic/sff8636/finisar.go @@ -0,0 +1 @@ +package sff8636 diff --git a/internal/pkg/optic/sff8636/flexoptix.go b/internal/pkg/optic/sff8636/flexoptix.go new file mode 100644 index 0000000..c40351c --- /dev/null +++ b/internal/pkg/optic/sff8636/flexoptix.go @@ -0,0 +1 @@ +package sff8636 diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go new file mode 100644 index 0000000..028c2ac --- /dev/null +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -0,0 +1,42 @@ +package sff8636 + +import ( + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" +) + +// State SFF8636 is a concrete implementation of the Management interface for SFF8636 spec +type State pkg.ModuleState + +func (s2 *State) Accepts(sff8024Identifier byte) bool { + return sff8024Identifier == 0x18 +} + +func (s2 *State) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { + //TODO implement me + panic("implement me") +} + +func (s2 *State) Get() (*pkg.ModuleState, error) { + //TODO implement me + panic("implement me") +} + +func (s2 *State) GetAdministrativeInformation() (*pkg.ModuleState, error) { + //TODO implement me + panic("implement me") +} + +func (s2 *State) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { + //TODO implement me + panic("implement me") +} + +func (s2 *State) GetTunableLaserCtrlStatus() (*pkg.ModuleState, error) { + //TODO implement me + panic("implement me") +} + +func (s2 *State) SetTunableLaserCtrlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { + //TODO implement me + panic("implement me") +} diff --git a/internal/pkg/rtbrick/bitmask.go b/internal/pkg/optic/util/bitmask.go similarity index 95% rename from internal/pkg/rtbrick/bitmask.go rename to internal/pkg/optic/util/bitmask.go index 253baf6..f854799 100644 --- a/internal/pkg/rtbrick/bitmask.go +++ b/internal/pkg/optic/util/bitmask.go @@ -1,4 +1,4 @@ -package rtbrick +package util const ( Bit0 Bitmask = 1 << iota diff --git a/internal/pkg/public.go b/internal/pkg/public.go new file mode 100644 index 0000000..23733ea --- /dev/null +++ b/internal/pkg/public.go @@ -0,0 +1,145 @@ +package pkg + +import ( + "encoding/json" + "fmt" + + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick/ssh" +) + +// FinIsarState client r/w interface for FinIsar specific settings +// delegates concrete operations to strategy +type FinIsarState struct { + // handle internal connection handle + handle *connection.I2cRWHandle +} + +// FlexOptixState client r/w interface for FlexOptix specific settings +// delegates concrete operations to strategy +type FlexOptixState struct { + // handle internal connection handle + handle *connection.I2cRWHandle +} + +// ModuleState is data exchange interface - delegates to strategy +// client can get / set freely from fields - not linked to pages or concrete implementation +// manufacturer specific fields can be read / set if struct is present, only one manufacturer +// will be present at a time +type ModuleState struct { + // mgmtProtoConcreteStrategy private pointer to concrete Strategy + mgmtProtoConcreteStrategy ConcreteManagementStrategy + // Handle pointer to connection Handle. + Handle *connection.I2cRWHandle + + // FinIsarSpecific public read only pointer to FinIsar manufacturer specific info + FinIsarSpecific *FinIsarState + // FlexOptixSpecific public read only pointer to FlexOptix manufacturer specific info + FlexOptixSpecific *FlexOptixState + + // SFF8024Identifier public read-only sff8024 id field + SFF8024Identifier byte +} + +func NewModuleState( + withDefaultStrategy ConcreteManagementStrategy, + withConcreteStrategies []ConcreteManagementStrategy, + withHandle *connection.I2cRWHandle, +) *ModuleState { + m := &ModuleState{ + Handle: withHandle, + mgmtProtoConcreteStrategy: withDefaultStrategy, + FinIsarSpecific: nil, + FlexOptixSpecific: nil, + } + + m, err := m.GetAdministrativeInformation() + if err != nil { + panic("Failed to query module for basic administrative information") + } + + for _, s := range withConcreteStrategies { + if s.Accepts(m.SFF8024Identifier) { + m.mgmtProtoConcreteStrategy = s + } + } + + if m.mgmtProtoConcreteStrategy == withDefaultStrategy { + panic(fmt.Sprintf("Unknown SFF8024 Management interface type %x", m.SFF8024Identifier)) + } + + return m +} + +func (m *ModuleState) SetStrategy(strategy ConcreteManagementStrategy) { + m.mgmtProtoConcreteStrategy = strategy +} + +func Json(m *ModuleState) { + ok, err := m.mgmtProtoConcreteStrategy.Get() + if err != nil { + return + } + marshal, err := json.Marshal(ok) + if err != nil { + return + } + fmt.Println(string(marshal)) +} + +func (m *ModuleState) Set(s *ModuleState) (*ModuleState, error) { + return m.mgmtProtoConcreteStrategy.Set(s) +} + +func (m *ModuleState) Get() (*ModuleState, error) { + return m.mgmtProtoConcreteStrategy.Get() +} + +func (m *ModuleState) GetAdministrativeInformation() (*ModuleState, error) { + return m.mgmtProtoConcreteStrategy.GetAdministrativeInformation() +} + +func (m *ModuleState) SetAdministrativeInformation(s *ModuleState) (*ModuleState, error) { + return m.mgmtProtoConcreteStrategy.SetAdministrativeInformation(s) +} + +func (m *ModuleState) GetTunableLaserCtrlStatus() (*ModuleState, error) { + return m.mgmtProtoConcreteStrategy.GetTunableLaserCtrlStatus() +} + +func (m *ModuleState) SetTunableLaserCtrlStatus(s *ModuleState) (*ModuleState, error) { + return m.mgmtProtoConcreteStrategy.SetTunableLaserCtrlStatus(s) +} + +// Management is implemented by ModuleState by delegating to strategies which have concrete implementations +type Management interface { + Set(s *ModuleState) (*ModuleState, error) + Get() (*ModuleState, error) + GetAdministrativeInformation() (*ModuleState, error) + SetAdministrativeInformation(s *ModuleState) (*ModuleState, error) + GetTunableLaserCtrlStatus() (*ModuleState, error) + SetTunableLaserCtrlStatus(s *ModuleState) (*ModuleState, error) +} + +// SFF8024Compatible to be implemented by concrete strategies +type SFF8024Compatible interface { + // Accepts tells if the strategy is compatible with the sff8024 type + Accepts(sff8024Identifier byte) bool +} + +// ConcreteManagementStrategy should implement both Management and SFF8024Compatible interfaces +type ConcreteManagementStrategy interface { + Management + SFF8024Compatible +} + +// FlexOptixManagement specific settings for FlexOptix modules. flex tune, nominal wavelength control +type FlexOptixManagement interface { + GetCustomFlexOptixSettings() (*FlexOptixState, error) + SetCustomFlexOptixSettings(s *FlexOptixState) (*FlexOptixState, error) +} + +// FinIsarManagement specific settings for FinIsar modules. TBD. +type FinIsarManagement interface { + GetCustomFinIsarSettings() (*FinIsarState, error) + SetCustomFinIsarSettings(s *FinIsarState) (*FinIsarState, error) +} diff --git a/internal/pkg/routines/routines.go b/internal/pkg/routines/routines.go index af162f1..0f130a8 100644 --- a/internal/pkg/routines/routines.go +++ b/internal/pkg/routines/routines.go @@ -4,59 +4,22 @@ import ( "context" "github.com/urfave/cli/v3" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick" - connection "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/ssh" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick/ssh" ) -var CmdStringToPowerMode = map[string]rtbrick.PowerMode{ - "high": rtbrick.PowerModeHighPower, - "low": rtbrick.PowerModeLowPower, -} - -type I2cRWHandle struct { - Connection *connection.RouterConnection - I2cBusId int -} - -func newI2cRWHandle(user string, router string, iface string) (*I2cRWHandle, error) { - handle := I2cRWHandle{} - - routerConnection, err := connection.New(user, router) - if err != nil { - return nil, err - } - err = routerConnection.Connect() - if err != nil { - return nil, err - } - _, ppdConfig, err := routerConnection.GetDeviceInformation() - if err != nil { - return nil, err - } - for _, port := range ppdConfig.Ports { - if port.Name == iface { - handle.I2cBusId = port.I2CBus - } - } - - handle.Connection = routerConnection - return &handle, nil -} - -func closeI2CRWHandle(handle *I2cRWHandle) { - err := handle.Connection.Close() - if err != nil { - panic(err) - } +var CmdStringToPowerMode = map[string]optic.PowerMode{ + "high": optic.PowerModeHighPower, + "low": optic.PowerModeLowPower, } type I2cActionArgs struct { - Handle *I2cRWHandle + Handle *connection.I2cRWHandle Cmd *cli.Command - Page00 *rtbrick.I2CPage00 - Page12 *rtbrick.I2CPage12 - Page1E *rtbrick.I2CPage1E - Page1B *rtbrick.I2CPageB0 + Page00 *optic.I2CPage00 + Page12 *optic.I2CPage12 + Page1E *optic.I2CPage1E + Page1B *optic.I2CPageB0 } type I2cAction func(args I2cActionArgs) error @@ -67,11 +30,11 @@ func I2cTemplateMethod(actions []I2cAction) cli.ActionFunc { router := cmd.StringArg("device") iface := cmd.StringArg("interface") - handle, err := newI2cRWHandle(user, router, iface) + handle, err := connection.NewI2cRWHandle(user, router, iface) if err != nil { return err } - defer closeI2CRWHandle(handle) + defer connection.CloseI2CRWHandle(handle) page00, err := handle.Connection.GetI2CDump(handle.I2cBusId, 0x00) if err != nil { @@ -90,10 +53,10 @@ func I2cTemplateMethod(actions []I2cAction) cli.ActionFunc { return err } - resultPage00 := rtbrick.InterpretPage00(page00) - resultPage12 := rtbrick.InterpretPage12(page12) - resultPage1E := rtbrick.InterpretPage1E(page1E) - resultPage1B := rtbrick.InterpretPageB0(page1B) + resultPage00 := optic.InterpretPage00(optic.ParseI2CDump(*page00)) + resultPage12 := optic.InterpretPage12(optic.ParseI2CDump(*page12)) + resultPage1E := optic.InterpretPage1E(optic.ParseI2CDump(*page1E)) + resultPage1B := optic.InterpretPageB0(optic.ParseI2CDump(*page1B)) actionArgs := I2cActionArgs{ Handle: handle, @@ -126,12 +89,12 @@ var I2CReadAll = I2cTemplateMethod(i2cReadActions[:]) var i2cWriteActions = [...]I2cAction{ ActionShowBasicAdminInfo, // some optics require disabling high power before programming - ActionSetPowerModeTo(rtbrick.PowerModeLowPower), - ActionShowFlexOptixCustomPages, - ActionDisableFlexTune, - ActionSetGridProgramming, - ActionEnableNominalWavelengthControl, - ActionSetPowerClassOverride, + ActionSetPowerModeTo(optic.PowerModeLowPower), + ActionShowFlexOptixCustomPages, // custom + ActionDisableFlexTune, // custom + ActionSetGridProgramming, // tunable laser + ActionEnableNominalWavelengthControl, // custom + ActionSetPowerClassOverride, // custom ActionSetPowerMode, } diff --git a/internal/pkg/routines/write_actions.go b/internal/pkg/routines/write_actions.go index f7aa00f..daf90d4 100644 --- a/internal/pkg/routines/write_actions.go +++ b/internal/pkg/routines/write_actions.go @@ -6,18 +6,18 @@ import ( "time" "github.com/pkg/errors" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic" ) -func ActionSetPowerModeTo(power rtbrick.PowerMode) I2cAction { - powerModeToDescription := map[rtbrick.PowerMode]string{ - rtbrick.PowerModeHighPower: "high", - rtbrick.PowerModeLowPower: "low", +func ActionSetPowerModeTo(power optic.PowerMode) I2cAction { + powerModeToDescription := map[optic.PowerMode]string{ + optic.PowerModeHighPower: "high", + optic.PowerModeLowPower: "low", } return func(args I2cActionArgs) error { slog.Info("set_power", slog.String("state", powerModeToDescription[power])) - wPage, wByte, wValue := rtbrick.GetPowerProgramming(power) + wPage, wByte, wValue := optic.GetPowerProgramming(power) err := args.Handle.Connection.DoI2CSet(args.Handle.I2cBusId, wPage, wByte, wValue) if err != nil { return err @@ -49,11 +49,11 @@ func ActionSetPowerClassOverride(args I2cActionArgs) error { return errors.New("power mode does not exist") } - wPage, wByte, wValue := rtbrick.GetPowerClassProgramming(false) // default low - if powerMode == rtbrick.PowerModeHighPower && args.Page1E.PowerClassOverride != 0x01 { - wPage, wByte, wValue = rtbrick.GetPowerClassProgramming(true) - } else if powerMode == rtbrick.PowerModeLowPower && args.Page1E.PowerClassOverride != 0x00 { - wPage, wByte, wValue = rtbrick.GetPowerClassProgramming(false) + wPage, wByte, wValue := optic.GetPowerClassProgramming(false) // default low + if powerMode == optic.PowerModeHighPower && args.Page1E.PowerClassOverride != 0x01 { + wPage, wByte, wValue = optic.GetPowerClassProgramming(true) + } else if powerMode == optic.PowerModeLowPower && args.Page1E.PowerClassOverride != 0x00 { + wPage, wByte, wValue = optic.GetPowerClassProgramming(false) } else { slog.Info("power_class", slog.String("mode", "already_set")) return nil @@ -74,7 +74,7 @@ func ActionDisableFlexTune(args I2cActionArgs) error { if args.Page1E.FlexTuneEnabled { slog.Info("Disabling Flex Tune...") - wPage, wByte, wValue := rtbrick.GetFlexTuneProgramming() + wPage, wByte, wValue := optic.GetFlexTuneProgramming() err := args.Handle.Connection.DoI2CSet(args.Handle.I2cBusId, wPage, wByte, wValue) if err != nil { return err @@ -101,7 +101,7 @@ func ActionSetGridProgramming(args I2cActionArgs) error { slog.Float64("target", gridSpacing), slog.String("current", args.Page12.GridDisplay), ) - wPage, wByte, wValue := rtbrick.GetGridProgramming(gridSpacing) + wPage, wByte, wValue := optic.GetGridProgramming(gridSpacing) err := args.Handle.Connection.DoI2CSet(args.Handle.I2cBusId, wPage, wByte, wValue) if err != nil { return err @@ -121,7 +121,7 @@ func ActionSetGridProgramming(args I2cActionArgs) error { slog.Int("current", *args.Page12.Channel), ) - wPage, wByte, wValue, wPage2, wByte2, wValue2 := rtbrick.GetChannelProgramming(gridSpacing, channel) + wPage, wByte, wValue, wPage2, wByte2, wValue2 := optic.GetChannelProgramming(gridSpacing, channel) err := args.Handle.Connection.DoI2CSet(args.Handle.I2cBusId, wPage, wByte, wValue) if err != nil { return err @@ -147,7 +147,7 @@ func ActionEnableNominalWavelengthControl(args I2cActionArgs) error { if !args.Page1B.NominalWavelengthControlEnabled { slog.Info("Setting Nominal Wavelength Control Programming...") - wPage, wByte, wValue := rtbrick.GetNominalWavelengthControlProgramming() + wPage, wByte, wValue := optic.GetNominalWavelengthControlProgramming() err := args.Handle.Connection.DoI2CSet(args.Handle.I2cBusId, wPage, wByte, wValue) if err != nil { return err diff --git a/internal/pkg/ssh/connection.go b/internal/pkg/rtbrick/ssh/connection.go similarity index 98% rename from internal/pkg/ssh/connection.go rename to internal/pkg/rtbrick/ssh/connection.go index 9a10011..efde749 100644 --- a/internal/pkg/ssh/connection.go +++ b/internal/pkg/rtbrick/ssh/connection.go @@ -155,7 +155,7 @@ func (r *RouterConnection) RunSSHCommand(command string) (string, error) { return stdoutBuffer.String(), nil } -func (r *RouterConnection) GetI2CDump(i2cbusId int, page byte) ([]byte, error) { +func (r *RouterConnection) GetI2CDump(i2cbusId int, page byte) (*string, error) { slog.Debug("page_dump_cmd", slog.String("hex_page", hex.EncodeToString([]byte{page}))) _, err := r.RunSSHCommand(fmt.Sprintf("sudo i2cset -y %d 0x50 127 %d", i2cbusId, page)) if err != nil { @@ -169,7 +169,7 @@ func (r *RouterConnection) GetI2CDump(i2cbusId int, page byte) ([]byte, error) { if err != nil { return nil, err } - return rtbrick.ParseI2CDump(out) + return &out, nil } func (r *RouterConnection) DoI2CSet(i2cbusId int, page int, byte int, value byte) error { diff --git a/internal/pkg/rtbrick/ssh/i2c_handle.go b/internal/pkg/rtbrick/ssh/i2c_handle.go new file mode 100644 index 0000000..bdcd44b --- /dev/null +++ b/internal/pkg/rtbrick/ssh/i2c_handle.go @@ -0,0 +1,38 @@ +package connection + +type I2cRWHandle struct { + Connection *RouterConnection + I2cBusId int +} + +func NewI2cRWHandle(user string, router string, iface string) (*I2cRWHandle, error) { + handle := I2cRWHandle{} + + routerConnection, err := New(user, router) + if err != nil { + return nil, err + } + err = routerConnection.Connect() + if err != nil { + return nil, err + } + _, ppdConfig, err := routerConnection.GetDeviceInformation() + if err != nil { + return nil, err + } + for _, port := range ppdConfig.Ports { + if port.Name == iface { + handle.I2cBusId = port.I2CBus + } + } + + handle.Connection = routerConnection + return &handle, nil +} + +func CloseI2CRWHandle(handle *I2cRWHandle) { + err := handle.Connection.Close() + if err != nil { + panic(err) + } +} From f16b0f5f58009045b432710c658a2cec6b71ff88 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Tue, 9 Jun 2026 11:08:10 +0200 Subject: [PATCH 03/36] implement module management compat check --- internal/pkg/optic/cmis/cmis.go | 35 +++++++++++++++++++++-- internal/pkg/optic/default.go | 19 ++++++++----- internal/pkg/optic/i2c.go | 35 ----------------------- internal/pkg/optic/sff8636/sff8636.go | 38 +++++++++++++++++++++++-- internal/pkg/public.go | 30 +++++++++++++++++--- internal/pkg/routines/routines.go | 9 +++--- internal/pkg/util.go | 40 +++++++++++++++++++++++++++ 7 files changed, 152 insertions(+), 54 deletions(-) create mode 100644 internal/pkg/util.go diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index 38a78cb..6196bc0 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -1,13 +1,44 @@ package cmis import ( + "slices" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" ) type State pkg.ModuleState -func (s2 *State) Accepts(sff8024Identifier byte) bool { - return sff8024Identifier == 0x11 +const RefusalMajorTooHigh string = "This CMIS module has" + + " a Major Revision number over what I can speak " + + "therefore it is unsupported. Program will be terminated " + + "now, as I cannot read nor write to this module without " + + "potential failure, data loss and/or equipment damage." + +func (s2 *State) Accepts(sff8024Identifier byte, sff8024Revision byte) bool { + var CmisCompatibleSFF8024IDs = [...]byte{ + 0x1E, // qsfp+ or later with cmis + 0x1F, // sfp-dd with cmis + 0x20, // sfp+ or later with cmis + 0x21, // osfp-xd with cmis + 0x22, // oif-elfs with cmis + 0x23, // 4 lanes cdfp with cmis + 0x24, // 8 lanes cdfp with cmis + 0x25, // 16 lanes cdfp with cmis + } + + const MajVerMask byte = 0b111_1000 // upper nibble is bits 7-4 per cmis rev 5.38 section 8.2.1 + const MinMajVer byte = 0x05 // minimum supported major version is 5 + + compatibleIdentifier := func(id byte) bool { return slices.Contains(CmisCompatibleSFF8024IDs[:], id) } + compatibleMajorRev := func(sff8024rev byte) bool { return (sff8024Identifier & MajVerMask) <= MinMajVer } + + // panic if CMIS and revision is above our maximum + if compatibleIdentifier(sff8024Identifier) && !compatibleMajorRev(sff8024Revision) { + panic(RefusalMajorTooHigh) + } + + // tells if compatible + return compatibleIdentifier(sff8024Identifier) } func (s2 *State) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { diff --git a/internal/pkg/optic/default.go b/internal/pkg/optic/default.go index 7a9c2be..2adeef8 100644 --- a/internal/pkg/optic/default.go +++ b/internal/pkg/optic/default.go @@ -7,25 +7,30 @@ import ( // State is the default Management strategy which should be set at initialization // it can only read page00, and does 0 write. It can tell which concrete strategy which you should // be using by looking at identifier codes and constructor ASCII -type State pkg.ModuleState +type State pkg.ModuleStateWithDirectPageAccess -func (d *State) Accepts(_ byte) bool { +func (d *State) Accepts(_ byte, _ byte) bool { return true } -func (d *State) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { - //TODO implement me +func (d *State) Set(_ *pkg.ModuleState) (*pkg.ModuleState, error) { panic(genericWriteErrorString) } func (d *State) Get() (*pkg.ModuleState, error) { - //TODO implement me panic(genericReadErrorString) } func (d *State) GetAdministrativeInformation() (*pkg.ModuleState, error) { - // TODO - panic("implement me") + bin, err := d.GetPageBin(0x00) + if err != nil { + return nil, err + } + + d.SFF8024Identifier = bin[0x00] + d.SFF8024Revision = bin[0x01] + + return &d.ModuleState, nil } func (d *State) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { diff --git a/internal/pkg/optic/i2c.go b/internal/pkg/optic/i2c.go index 7409ec6..3c302c5 100644 --- a/internal/pkg/optic/i2c.go +++ b/internal/pkg/optic/i2c.go @@ -1,10 +1,7 @@ package optic import ( - "bytes" "encoding/binary" - "encoding/hex" - "log/slog" "math" "strconv" "strings" @@ -12,38 +9,6 @@ import ( "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" ) -func ParseI2CDump(dump string) []byte { - slog.Debug("======== I2C Dump ========") - slog.Debug("\n" + dump) - slog.Debug("======== ======== ========") - - lines := strings.Split(dump, "\n") - - buf := make([]byte, 0, 1024) - w := bytes.NewBuffer(buf) - - for _, line := range lines[1:17] { - - p1 := strings.Split(line, ": ")[1] - p2 := strings.Split(p1, " ")[0] - - for _, x := range strings.Split(p2, " ") { - b, err := hex.DecodeString(x) - if err != nil { - slog.Error("could not parse", "code", err) - panic(err) - } - w.Write(b) - } - - } - - allBytes := w.Bytes() - slog.Debug("raw_decoded_i2c_bytes", slog.String("hex_string", hex.EncodeToString(allBytes))) - return allBytes - -} - func keysByValue[K comparable, V comparable](m map[K]V, value V) *K { for k, v := range m { if value == v { diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index 028c2ac..e7248b7 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -1,14 +1,48 @@ package sff8636 import ( + "slices" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" ) // State SFF8636 is a concrete implementation of the Management interface for SFF8636 spec type State pkg.ModuleState -func (s2 *State) Accepts(sff8024Identifier byte) bool { - return sff8024Identifier == 0x18 +const RefusalVerMismatch = "This module has an " + + "SFF8636 Revision number that I do not know of, therefore it is unsupported. Program will be terminated now, " + + "as I cannot read nor write to this module without potential failure, data loss and/or equipment damage." + +func (s2 *State) Accepts(sff8024Identifier byte, sff8024Revision byte) bool { + var SFF8636CompatibleSFF8024IDs = [...]byte{ + 0x0D, // qsfp+ or later with sff8646 or sff8436 mgmt interface + 0x11, // qsfp28 or later with sff-8636 management interface + } + + var CompatibleSFF8638Versions = [...]byte{ + // 0x00 not compatible, do not use for rev 2.5 or higher + // 0x01 sff 8436 not compatible + // 0x02 sff 8436 not compatible + 0x03, // 1.3 or earlier + 0x04, // 1.4 + 0x05, // 1.5 + 0x06, // 2.0 + 0x07, // 2.5, 2.6 and 2.7 + 0x08, // 2.8, 2.9 and 2.10 + 0x09, // 2.11 + 0x0A, // 2.12 + } + + compatibleIdentifier := func(id byte) bool { return slices.Contains(SFF8636CompatibleSFF8024IDs[:], id) } + compatibleRev := func(sff8024rev byte) bool { return slices.Contains(CompatibleSFF8638Versions[:], sff8024rev) } + + // panic if revision is unknown + if compatibleIdentifier(sff8024Identifier) && !compatibleRev(sff8024Revision) { + panic(RefusalVerMismatch) + } + + // tells if compatible + return compatibleIdentifier(sff8024Identifier) } func (s2 *State) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { diff --git a/internal/pkg/public.go b/internal/pkg/public.go index 23733ea..f078191 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -38,6 +38,8 @@ type ModuleState struct { // SFF8024Identifier public read-only sff8024 id field SFF8024Identifier byte + // SFF8024Revision public read-only sff8024 revision id field + SFF8024Revision byte } func NewModuleState( @@ -58,7 +60,7 @@ func NewModuleState( } for _, s := range withConcreteStrategies { - if s.Accepts(m.SFF8024Identifier) { + if s.Accepts(m.SFF8024Identifier, 0) { m.mgmtProtoConcreteStrategy = s } } @@ -70,8 +72,17 @@ func NewModuleState( return m } -func (m *ModuleState) SetStrategy(strategy ConcreteManagementStrategy) { - m.mgmtProtoConcreteStrategy = strategy +func (m *ModuleStateWithDirectPageAccess) GetPageBin(page byte) ([]byte, error) { + pageStr, err := m.Handle.Connection.GetI2CDump(m.Handle.I2cBusId, page) + if err != nil { + return []byte{}, err + } + return ParseI2CDump(*pageStr), nil +} + +func (m *ModuleStateWithDirectPageAccess) WritePageBin(page byte, offset byte, value byte) error { + // TODO implement me + panic("not implemented.") } func Json(m *ModuleState) { @@ -120,10 +131,21 @@ type Management interface { SetTunableLaserCtrlStatus(s *ModuleState) (*ModuleState, error) } +// DirectPageAccess Allows direct read/write access to pages +type DirectPageAccess interface { + GetPageBin(page byte) ([]byte, error) + WritePageBin(page byte, offset byte, value byte) error +} + +type ModuleStateWithDirectPageAccess struct { + ModuleState + DirectPageAccess +} + // SFF8024Compatible to be implemented by concrete strategies type SFF8024Compatible interface { // Accepts tells if the strategy is compatible with the sff8024 type - Accepts(sff8024Identifier byte) bool + Accepts(sff8024Identifier byte, sff8024Revision byte) bool } // ConcreteManagementStrategy should implement both Management and SFF8024Compatible interfaces diff --git a/internal/pkg/routines/routines.go b/internal/pkg/routines/routines.go index 0f130a8..186b71a 100644 --- a/internal/pkg/routines/routines.go +++ b/internal/pkg/routines/routines.go @@ -4,6 +4,7 @@ import ( "context" "github.com/urfave/cli/v3" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick/ssh" ) @@ -53,10 +54,10 @@ func I2cTemplateMethod(actions []I2cAction) cli.ActionFunc { return err } - resultPage00 := optic.InterpretPage00(optic.ParseI2CDump(*page00)) - resultPage12 := optic.InterpretPage12(optic.ParseI2CDump(*page12)) - resultPage1E := optic.InterpretPage1E(optic.ParseI2CDump(*page1E)) - resultPage1B := optic.InterpretPageB0(optic.ParseI2CDump(*page1B)) + resultPage00 := optic.InterpretPage00(pkg.ParseI2CDump(*page00)) + resultPage12 := optic.InterpretPage12(pkg.ParseI2CDump(*page12)) + resultPage1E := optic.InterpretPage1E(pkg.ParseI2CDump(*page1E)) + resultPage1B := optic.InterpretPageB0(pkg.ParseI2CDump(*page1B)) actionArgs := I2cActionArgs{ Handle: handle, diff --git a/internal/pkg/util.go b/internal/pkg/util.go new file mode 100644 index 0000000..a0e4a15 --- /dev/null +++ b/internal/pkg/util.go @@ -0,0 +1,40 @@ +package pkg + +import ( + "bytes" + "encoding/hex" + "log/slog" + "strings" +) + +func ParseI2CDump(dump string) []byte { + slog.Debug("======== I2C Dump ========") + slog.Debug("\n" + dump) + slog.Debug("======== ======== ========") + + lines := strings.Split(dump, "\n") + + buf := make([]byte, 0, 1024) + w := bytes.NewBuffer(buf) + + for _, line := range lines[1:17] { + + p1 := strings.Split(line, ": ")[1] + p2 := strings.Split(p1, " ")[0] + + for _, x := range strings.Split(p2, " ") { + b, err := hex.DecodeString(x) + if err != nil { + slog.Error("could not parse", "code", err) + panic(err) + } + w.Write(b) + } + + } + + allBytes := w.Bytes() + slog.Debug("raw_decoded_i2c_bytes", slog.String("hex_string", hex.EncodeToString(allBytes))) + return allBytes + +} From bae9d46c148a93c84396264cb034632c257d353b Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Tue, 9 Jun 2026 15:06:39 +0200 Subject: [PATCH 04/36] implement sff8636 basic admin info get --- internal/pkg/optic/cmis/cmis.go | 2 +- internal/pkg/optic/default.go | 2 +- internal/pkg/optic/i2c.go | 15 +++----------- internal/pkg/optic/sff8636/sff8636.go | 30 +++++++++++++++++++++++---- internal/pkg/optic/util/ascii.go | 11 ++++++++++ internal/pkg/public.go | 17 ++++++++++----- 6 files changed, 54 insertions(+), 23 deletions(-) create mode 100644 internal/pkg/optic/util/ascii.go diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index 6196bc0..fb8684b 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -14,7 +14,7 @@ const RefusalMajorTooHigh string = "This CMIS module has" + "now, as I cannot read nor write to this module without " + "potential failure, data loss and/or equipment damage." -func (s2 *State) Accepts(sff8024Identifier byte, sff8024Revision byte) bool { +func (s2 *State) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { var CmisCompatibleSFF8024IDs = [...]byte{ 0x1E, // qsfp+ or later with cmis 0x1F, // sfp-dd with cmis diff --git a/internal/pkg/optic/default.go b/internal/pkg/optic/default.go index 2adeef8..f2e735a 100644 --- a/internal/pkg/optic/default.go +++ b/internal/pkg/optic/default.go @@ -9,7 +9,7 @@ import ( // be using by looking at identifier codes and constructor ASCII type State pkg.ModuleStateWithDirectPageAccess -func (d *State) Accepts(_ byte, _ byte) bool { +func (d *State) AcceptsSFF8024(_ byte, _ byte) bool { return true } diff --git a/internal/pkg/optic/i2c.go b/internal/pkg/optic/i2c.go index 3c302c5..6144751 100644 --- a/internal/pkg/optic/i2c.go +++ b/internal/pkg/optic/i2c.go @@ -4,7 +4,6 @@ import ( "encoding/binary" "math" "strconv" - "strings" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" ) @@ -36,14 +35,6 @@ func ToTwoComplement16(a int16) []byte { return b } -func ParseASCIIToString(part []byte) string { - var asciiString string - for _, code := range part { - asciiString += string(rune(code)) - } - return strings.TrimSpace(asciiString) -} - type I2CPage12Grid int const ( @@ -240,9 +231,9 @@ func InterpretPage00(dump []byte) I2CPage00 { return I2CPage00{ LowPowerMode: isLowPowerMode, - VendorName: ParseASCIIToString(dump[148:164]), - VendorPN: ParseASCIIToString(dump[168:184]), - VendorSN: ParseASCIIToString(dump[196:212]), + VendorName: util.ParseASCIIToString(dump[148:164]), + VendorPN: util.ParseASCIIToString(dump[168:184]), + VendorSN: util.ParseASCIIToString(dump[196:212]), } } diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index e7248b7..80b751a 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -4,16 +4,17 @@ import ( "slices" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" ) // State SFF8636 is a concrete implementation of the Management interface for SFF8636 spec -type State pkg.ModuleState +type State pkg.ModuleStateWithDirectPageAccess const RefusalVerMismatch = "This module has an " + "SFF8636 Revision number that I do not know of, therefore it is unsupported. Program will be terminated now, " + "as I cannot read nor write to this module without potential failure, data loss and/or equipment damage." -func (s2 *State) Accepts(sff8024Identifier byte, sff8024Revision byte) bool { +func (s2 *State) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { var SFF8636CompatibleSFF8024IDs = [...]byte{ 0x0D, // qsfp+ or later with sff8646 or sff8436 mgmt interface 0x11, // qsfp28 or later with sff-8636 management interface @@ -51,13 +52,34 @@ func (s2 *State) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { } func (s2 *State) Get() (*pkg.ModuleState, error) { + _, err := s2.GetAdministrativeInformation() + if err != nil { + return nil, err + } + //TODO implement me panic("implement me") + + return &s2.ModuleState, nil } func (s2 *State) GetAdministrativeInformation() (*pkg.ModuleState, error) { - //TODO implement me - panic("implement me") + bin, err := s2.GetPageBin(0x00) + if err != nil { + return nil, err + } + + s2.ManagementProtocol = "sff8636" + // lower mem + s2.SFF8024Identifier = bin[0x00] + s2.SFF8024Revision = bin[0x01] + // page 0x00 + s2.VendorName = util.ParseASCIIToString(bin[0x94:0xA3]) + s2.VendorPartNumber = util.ParseASCIIToString(bin[0xA8:0xB7]) + s2.VendorPartRevision = util.ParseASCIIToString(bin[0xA8:0xB7]) + s2.VendorSerialNumber = util.ParseASCIIToString(bin[0xC4:0xD3]) + + return &s2.ModuleState, nil } func (s2 *State) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { diff --git a/internal/pkg/optic/util/ascii.go b/internal/pkg/optic/util/ascii.go new file mode 100644 index 0000000..d6e80a4 --- /dev/null +++ b/internal/pkg/optic/util/ascii.go @@ -0,0 +1,11 @@ +package util + +import "strings" + +func ParseASCIIToString(part []byte) string { + var asciiString string + for _, code := range part { + asciiString += string(rune(code)) + } + return strings.TrimSpace(asciiString) +} diff --git a/internal/pkg/public.go b/internal/pkg/public.go index f078191..5432786 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -36,10 +36,16 @@ type ModuleState struct { // FlexOptixSpecific public read only pointer to FlexOptix manufacturer specific info FlexOptixSpecific *FlexOptixState - // SFF8024Identifier public read-only sff8024 id field + ManagementProtocol string + // SFF8024Identifier lower mem public read-only sff8024 id field SFF8024Identifier byte - // SFF8024Revision public read-only sff8024 revision id field + // SFF8024Revision lower mem public read-only sff8024 revision id field SFF8024Revision byte + + VendorName string + VendorPartNumber string + VendorPartRevision string + VendorSerialNumber string } func NewModuleState( @@ -52,6 +58,7 @@ func NewModuleState( mgmtProtoConcreteStrategy: withDefaultStrategy, FinIsarSpecific: nil, FlexOptixSpecific: nil, + ManagementProtocol: "unknown", } m, err := m.GetAdministrativeInformation() @@ -60,7 +67,7 @@ func NewModuleState( } for _, s := range withConcreteStrategies { - if s.Accepts(m.SFF8024Identifier, 0) { + if s.AcceptsSFF8024(m.SFF8024Identifier, 0) { m.mgmtProtoConcreteStrategy = s } } @@ -144,8 +151,8 @@ type ModuleStateWithDirectPageAccess struct { // SFF8024Compatible to be implemented by concrete strategies type SFF8024Compatible interface { - // Accepts tells if the strategy is compatible with the sff8024 type - Accepts(sff8024Identifier byte, sff8024Revision byte) bool + // AcceptsSFF8024 tells if the strategy is compatible with the sff8024 type + AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool } // ConcreteManagementStrategy should implement both Management and SFF8024Compatible interfaces From 83d638d336c9b2a3ca32381a59593888fd77fab3 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Tue, 9 Jun 2026 16:45:05 +0200 Subject: [PATCH 05/36] switch over to new arch for show command (wip) --- cmd/optic-programmer.go | 45 +++++++++++++- internal/pkg/optic/cmis/cmis.go | 24 +++++--- internal/pkg/optic/{ => default}/default.go | 36 ++++++----- internal/pkg/optic/sff8636/sff8636.go | 51 +++++++++------- internal/pkg/public.go | 67 +++++++++------------ 5 files changed, 140 insertions(+), 83 deletions(-) rename internal/pkg/optic/{ => default}/default.go (51%) diff --git a/cmd/optic-programmer.go b/cmd/optic-programmer.go index 2d3a8c2..737a70d 100644 --- a/cmd/optic-programmer.go +++ b/cmd/optic-programmer.go @@ -7,7 +7,12 @@ import ( "strings" "github.com/urfave/cli/v3" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/cmis" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/default" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/sff8636" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/routines" + connection "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick/ssh" ) func getLoglevel(level string) slog.Level { @@ -24,6 +29,15 @@ func getLoglevel(level string) slog.Level { panic("invalid log level provided") } +var concreteManagementStrategies = [...]func(state *pkg.ModuleState) pkg.ConcreteManagementStrategy{ + func(state *pkg.ModuleState) pkg.ConcreteManagementStrategy { return sff8636.New(state) }, + func(state *pkg.ModuleState) pkg.ConcreteManagementStrategy { return cmis.New(state) }, +} + +var defaultManagementStrategy = func(state *pkg.ModuleState) pkg.ConcreteManagementStrategy { + return _default.New(state) +} + func main() { slog.SetLogLoggerLevel(slog.LevelInfo) if level, ok := os.LookupEnv("LOG_LEVEL"); ok { @@ -39,6 +53,7 @@ func main() { }, Commands: []*cli.Command{ { + Name: "show", Aliases: []string{"s"}, Usage: "Shows information about an optic in a specific device", @@ -50,7 +65,35 @@ func main() { Name: "interface", }, }, - Action: routines.I2CReadAll, + Action: func(ctx context.Context, command *cli.Command) error { + user := command.String("user") + router := command.StringArg("device") + iface := command.StringArg("interface") + + handle, err := connection.NewI2cRWHandle(user, router, iface) + if err != nil { + return err + } + defer connection.CloseI2CRWHandle(handle) + + module, err := pkg.NewModuleState( + defaultManagementStrategy, + concreteManagementStrategies[:], + handle, + ).Get() + if err != nil { + return err + } + bytes, err := module.ToJson() + if err != nil { + return err + } + _, err = os.Stdout.Write(bytes) + if err != nil { + return err + } + return nil + }, }, { Name: "program", diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index fb8684b..1ffd388 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -6,7 +6,15 @@ import ( "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" ) -type State pkg.ModuleState +type ManagementStrategy struct { + state *pkg.ModuleState +} + +func New(state *pkg.ModuleState) *ManagementStrategy { + return &ManagementStrategy{ + state: state, + } +} const RefusalMajorTooHigh string = "This CMIS module has" + " a Major Revision number over what I can speak " + @@ -14,7 +22,7 @@ const RefusalMajorTooHigh string = "This CMIS module has" + "now, as I cannot read nor write to this module without " + "potential failure, data loss and/or equipment damage." -func (s2 *State) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { +func (s2 ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { var CmisCompatibleSFF8024IDs = [...]byte{ 0x1E, // qsfp+ or later with cmis 0x1F, // sfp-dd with cmis @@ -41,32 +49,32 @@ func (s2 *State) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bo return compatibleIdentifier(sff8024Identifier) } -func (s2 *State) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 ManagementStrategy) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } -func (s2 *State) Get() (*pkg.ModuleState, error) { +func (s2 ManagementStrategy) Get() (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } -func (s2 *State) GetAdministrativeInformation() (*pkg.ModuleState, error) { +func (s2 ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } -func (s2 *State) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } -func (s2 *State) GetTunableLaserCtrlStatus() (*pkg.ModuleState, error) { +func (s2 ManagementStrategy) GetTunableLaserCtrlStatus() (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } -func (s2 *State) SetTunableLaserCtrlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 ManagementStrategy) SetTunableLaserCtrlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } diff --git a/internal/pkg/optic/default.go b/internal/pkg/optic/default/default.go similarity index 51% rename from internal/pkg/optic/default.go rename to internal/pkg/optic/default/default.go index f2e735a..7cda910 100644 --- a/internal/pkg/optic/default.go +++ b/internal/pkg/optic/default/default.go @@ -1,47 +1,55 @@ -package optic +package _default import ( "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" ) -// State is the default Management strategy which should be set at initialization +// ManagementStrategy is the default Management strategy which should be set at initialization // it can only read page00, and does 0 write. It can tell which concrete strategy which you should // be using by looking at identifier codes and constructor ASCII -type State pkg.ModuleStateWithDirectPageAccess +type ManagementStrategy struct { + state *pkg.ModuleState +} + +func New(state *pkg.ModuleState) *ManagementStrategy { + return &ManagementStrategy{ + state: state, + } +} -func (d *State) AcceptsSFF8024(_ byte, _ byte) bool { +func (d ManagementStrategy) AcceptsSFF8024(_ byte, _ byte) bool { return true } -func (d *State) Set(_ *pkg.ModuleState) (*pkg.ModuleState, error) { +func (d ManagementStrategy) Set(_ *pkg.ModuleState) (*pkg.ModuleState, error) { panic(genericWriteErrorString) } -func (d *State) Get() (*pkg.ModuleState, error) { +func (d ManagementStrategy) Get() (*pkg.ModuleState, error) { panic(genericReadErrorString) } -func (d *State) GetAdministrativeInformation() (*pkg.ModuleState, error) { - bin, err := d.GetPageBin(0x00) +func (d ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { + bin, err := d.state.GetPageBin(0x00) if err != nil { return nil, err } - d.SFF8024Identifier = bin[0x00] - d.SFF8024Revision = bin[0x01] + d.state.SFF8024Identifier = bin[0x00] + d.state.SFF8024Revision = bin[0x01] - return &d.ModuleState, nil + return d.state, nil } -func (d *State) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (d ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { panic(genericWriteErrorString) } -func (d *State) GetTunableLaserCtrlStatus() (*pkg.ModuleState, error) { +func (d ManagementStrategy) GetTunableLaserCtrlStatus() (*pkg.ModuleState, error) { panic(genericReadErrorString) } -func (d *State) SetTunableLaserCtrlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (d ManagementStrategy) SetTunableLaserCtrlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { panic(genericWriteErrorString) } diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index 80b751a..55975b7 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -7,14 +7,22 @@ import ( "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" ) -// State SFF8636 is a concrete implementation of the Management interface for SFF8636 spec -type State pkg.ModuleStateWithDirectPageAccess +// ManagementStrategy SFF8636 is a concrete implementation of the Management interface for SFF8636 spec +type ManagementStrategy struct { + state *pkg.ModuleState +} + +func New(state *pkg.ModuleState) *ManagementStrategy { + return &ManagementStrategy{ + state: state, + } +} const RefusalVerMismatch = "This module has an " + "SFF8636 Revision number that I do not know of, therefore it is unsupported. Program will be terminated now, " + "as I cannot read nor write to this module without potential failure, data loss and/or equipment damage." -func (s2 *State) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { +func (s2 ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { var SFF8636CompatibleSFF8024IDs = [...]byte{ 0x0D, // qsfp+ or later with sff8646 or sff8436 mgmt interface 0x11, // qsfp28 or later with sff-8636 management interface @@ -46,53 +54,50 @@ func (s2 *State) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bo return compatibleIdentifier(sff8024Identifier) } -func (s2 *State) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 ManagementStrategy) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } -func (s2 *State) Get() (*pkg.ModuleState, error) { +func (s2 ManagementStrategy) Get() (*pkg.ModuleState, error) { _, err := s2.GetAdministrativeInformation() if err != nil { return nil, err } - - //TODO implement me - panic("implement me") - - return &s2.ModuleState, nil + // TODO call the rest of getters + return s2.state, nil } -func (s2 *State) GetAdministrativeInformation() (*pkg.ModuleState, error) { - bin, err := s2.GetPageBin(0x00) +func (s2 ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { + bin, err := s2.state.GetPageBin(0x00) if err != nil { return nil, err } - s2.ManagementProtocol = "sff8636" + s2.state.ManagementProtocol = "sff8636" // lower mem - s2.SFF8024Identifier = bin[0x00] - s2.SFF8024Revision = bin[0x01] + s2.state.SFF8024Identifier = bin[0x00] + s2.state.SFF8024Revision = bin[0x01] // page 0x00 - s2.VendorName = util.ParseASCIIToString(bin[0x94:0xA3]) - s2.VendorPartNumber = util.ParseASCIIToString(bin[0xA8:0xB7]) - s2.VendorPartRevision = util.ParseASCIIToString(bin[0xA8:0xB7]) - s2.VendorSerialNumber = util.ParseASCIIToString(bin[0xC4:0xD3]) + s2.state.VendorName = util.ParseASCIIToString(bin[0x94:0xA3]) + s2.state.VendorPartNumber = util.ParseASCIIToString(bin[0xA8:0xB7]) + s2.state.VendorPartRevision = util.ParseASCIIToString(bin[0xA8:0xB7]) + s2.state.VendorSerialNumber = util.ParseASCIIToString(bin[0xC4:0xD3]) - return &s2.ModuleState, nil + return s2.state, nil } -func (s2 *State) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } -func (s2 *State) GetTunableLaserCtrlStatus() (*pkg.ModuleState, error) { +func (s2 ManagementStrategy) GetTunableLaserCtrlStatus() (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } -func (s2 *State) SetTunableLaserCtrlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 ManagementStrategy) SetTunableLaserCtrlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } diff --git a/internal/pkg/public.go b/internal/pkg/public.go index 5432786..90043d2 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -3,6 +3,7 @@ package pkg import ( "encoding/json" "fmt" + "reflect" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick/ssh" ) @@ -28,8 +29,8 @@ type FlexOptixState struct { type ModuleState struct { // mgmtProtoConcreteStrategy private pointer to concrete Strategy mgmtProtoConcreteStrategy ConcreteManagementStrategy - // Handle pointer to connection Handle. - Handle *connection.I2cRWHandle + // handle pointer to connection handle. + handle *connection.I2cRWHandle // FinIsarSpecific public read only pointer to FinIsar manufacturer specific info FinIsarSpecific *FinIsarState @@ -38,9 +39,9 @@ type ModuleState struct { ManagementProtocol string // SFF8024Identifier lower mem public read-only sff8024 id field - SFF8024Identifier byte + SFF8024Identifier uint8 // SFF8024Revision lower mem public read-only sff8024 revision id field - SFF8024Revision byte + SFF8024Revision uint8 VendorName string VendorPartNumber string @@ -49,82 +50,79 @@ type ModuleState struct { } func NewModuleState( - withDefaultStrategy ConcreteManagementStrategy, - withConcreteStrategies []ConcreteManagementStrategy, + withDefaultStrategyFactory func(state *ModuleState) ConcreteManagementStrategy, + withConcreteStrategiesFactories []func(state *ModuleState) ConcreteManagementStrategy, withHandle *connection.I2cRWHandle, ) *ModuleState { m := &ModuleState{ - Handle: withHandle, - mgmtProtoConcreteStrategy: withDefaultStrategy, - FinIsarSpecific: nil, - FlexOptixSpecific: nil, - ManagementProtocol: "unknown", + handle: withHandle, + FinIsarSpecific: nil, + FlexOptixSpecific: nil, + ManagementProtocol: "unknown", } + m.mgmtProtoConcreteStrategy = withDefaultStrategyFactory(m) // self-referential m, err := m.GetAdministrativeInformation() if err != nil { panic("Failed to query module for basic administrative information") } - for _, s := range withConcreteStrategies { - if s.AcceptsSFF8024(m.SFF8024Identifier, 0) { - m.mgmtProtoConcreteStrategy = s + for _, s := range withConcreteStrategiesFactories { + if s(m).AcceptsSFF8024(m.SFF8024Identifier, m.SFF8024Revision) { + m.mgmtProtoConcreteStrategy = s(m) } } - if m.mgmtProtoConcreteStrategy == withDefaultStrategy { + if reflect.TypeOf(m.mgmtProtoConcreteStrategy) == reflect.TypeOf(withDefaultStrategyFactory(m)) { panic(fmt.Sprintf("Unknown SFF8024 Management interface type %x", m.SFF8024Identifier)) } return m } -func (m *ModuleStateWithDirectPageAccess) GetPageBin(page byte) ([]byte, error) { - pageStr, err := m.Handle.Connection.GetI2CDump(m.Handle.I2cBusId, page) +func (m ModuleState) GetPageBin(page byte) ([]byte, error) { + pageStr, err := m.handle.Connection.GetI2CDump(m.handle.I2cBusId, page) if err != nil { return []byte{}, err } return ParseI2CDump(*pageStr), nil } -func (m *ModuleStateWithDirectPageAccess) WritePageBin(page byte, offset byte, value byte) error { +func (m ModuleState) WritePageBin(page byte, offset byte, value byte) error { // TODO implement me panic("not implemented.") } -func Json(m *ModuleState) { - ok, err := m.mgmtProtoConcreteStrategy.Get() - if err != nil { - return - } - marshal, err := json.Marshal(ok) +func (m ModuleState) ToJson() ([]byte, error) { + marshal, err := json.MarshalIndent(m, "", "\t") if err != nil { - return + return nil, err } - fmt.Println(string(marshal)) + + return marshal, nil } -func (m *ModuleState) Set(s *ModuleState) (*ModuleState, error) { +func (m ModuleState) Set(s *ModuleState) (*ModuleState, error) { return m.mgmtProtoConcreteStrategy.Set(s) } -func (m *ModuleState) Get() (*ModuleState, error) { +func (m ModuleState) Get() (*ModuleState, error) { return m.mgmtProtoConcreteStrategy.Get() } -func (m *ModuleState) GetAdministrativeInformation() (*ModuleState, error) { +func (m ModuleState) GetAdministrativeInformation() (*ModuleState, error) { return m.mgmtProtoConcreteStrategy.GetAdministrativeInformation() } -func (m *ModuleState) SetAdministrativeInformation(s *ModuleState) (*ModuleState, error) { +func (m ModuleState) SetAdministrativeInformation(s *ModuleState) (*ModuleState, error) { return m.mgmtProtoConcreteStrategy.SetAdministrativeInformation(s) } -func (m *ModuleState) GetTunableLaserCtrlStatus() (*ModuleState, error) { +func (m ModuleState) GetTunableLaserCtrlStatus() (*ModuleState, error) { return m.mgmtProtoConcreteStrategy.GetTunableLaserCtrlStatus() } -func (m *ModuleState) SetTunableLaserCtrlStatus(s *ModuleState) (*ModuleState, error) { +func (m ModuleState) SetTunableLaserCtrlStatus(s *ModuleState) (*ModuleState, error) { return m.mgmtProtoConcreteStrategy.SetTunableLaserCtrlStatus(s) } @@ -144,11 +142,6 @@ type DirectPageAccess interface { WritePageBin(page byte, offset byte, value byte) error } -type ModuleStateWithDirectPageAccess struct { - ModuleState - DirectPageAccess -} - // SFF8024Compatible to be implemented by concrete strategies type SFF8024Compatible interface { // AcceptsSFF8024 tells if the strategy is compatible with the sff8024 type From 50b148f9f71479b1b6f561a73600283e16c9146c Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Tue, 9 Jun 2026 17:32:37 +0200 Subject: [PATCH 06/36] implement rest of relevant lower mem queries --- internal/pkg/optic/sff8636/sff8636.go | 13 +++++++++++ internal/pkg/public.go | 33 ++++++++++++++------------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index 55975b7..9f9c9c5 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -69,6 +69,13 @@ func (s2 ManagementStrategy) Get() (*pkg.ModuleState, error) { } func (s2 ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { + // register 0x5D lower mem masks + const SoftwareResetMask = 0b1000_0000 + const EnableHighPowerClass8Mask = 0b0000_1000 + const EnableHighPowerClass57Mask = 0b0000_0100 + const LowPwrRequestSWMask = 0b0000_0010 + const LowPwrOverrideMask = 0b0000_0001 + bin, err := s2.state.GetPageBin(0x00) if err != nil { return nil, err @@ -78,6 +85,12 @@ func (s2 ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, e // lower mem s2.state.SFF8024Identifier = bin[0x00] s2.state.SFF8024Revision = bin[0x01] + s2.state.SoftwareReset = bin[0x5D]&SoftwareResetMask == SoftwareResetMask + s2.state.EnableHighPowerClass8 = bin[0x5D]&EnableHighPowerClass8Mask == EnableHighPowerClass8Mask + s2.state.EnableHighPowerClass57 = bin[0x5D]&EnableHighPowerClass57Mask == EnableHighPowerClass57Mask + s2.state.LowPwrRequestSW = bin[0x5D]&LowPwrRequestSWMask == LowPwrRequestSWMask + s2.state.LowPwrOverride = bin[0x5D]&LowPwrOverrideMask == LowPwrOverrideMask + // page 0x00 s2.state.VendorName = util.ParseASCIIToString(bin[0x94:0xA3]) s2.state.VendorPartNumber = util.ParseASCIIToString(bin[0xA8:0xB7]) diff --git a/internal/pkg/public.go b/internal/pkg/public.go index 90043d2..9288bed 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -27,22 +27,23 @@ type FlexOptixState struct { // manufacturer specific fields can be read / set if struct is present, only one manufacturer // will be present at a time type ModuleState struct { - // mgmtProtoConcreteStrategy private pointer to concrete Strategy - mgmtProtoConcreteStrategy ConcreteManagementStrategy - // handle pointer to connection handle. - handle *connection.I2cRWHandle - - // FinIsarSpecific public read only pointer to FinIsar manufacturer specific info - FinIsarSpecific *FinIsarState - // FlexOptixSpecific public read only pointer to FlexOptix manufacturer specific info - FlexOptixSpecific *FlexOptixState - - ManagementProtocol string - // SFF8024Identifier lower mem public read-only sff8024 id field - SFF8024Identifier uint8 - // SFF8024Revision lower mem public read-only sff8024 revision id field - SFF8024Revision uint8 - + mgmtProtoConcreteStrategy ConcreteManagementStrategy // private pointer to concrete Strategy + handle *connection.I2cRWHandle // private pointer to connection handle. + + FinIsarSpecific *FinIsarState // public read only pointer to FinIsar manufacturer specific info + FlexOptixSpecific *FlexOptixState // public read only pointer to FlexOptix manufacturer specific info + + // lower mem region + ManagementProtocol string + SFF8024Identifier uint8 // SFF8024Identifier lower mem public read-only sff8024 id field + SFF8024Revision uint8 // SFF8024Revision lower mem public read-only sff8024 revision id field + SoftwareReset bool + EnableHighPowerClass8 bool + EnableHighPowerClass57 bool + LowPwrRequestSW bool + LowPwrOverride bool + + // page 00 region VendorName string VendorPartNumber string VendorPartRevision string From efd7d3a90e7be4f2b1e563cdf7162ef68871698b Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 10 Jun 2026 15:14:06 +0200 Subject: [PATCH 07/36] implement protocol extension arch --- cmd/optic-programmer.go | 10 +++ internal/pkg/optic/sff8636/flexoptix.go | 41 +++++++++ internal/pkg/optic/sff8636/sff8636.go | 40 +++++++++ internal/pkg/public.go | 115 ++++++++++++++++++------ 4 files changed, 180 insertions(+), 26 deletions(-) diff --git a/cmd/optic-programmer.go b/cmd/optic-programmer.go index 737a70d..f2e26aa 100644 --- a/cmd/optic-programmer.go +++ b/cmd/optic-programmer.go @@ -34,6 +34,15 @@ var concreteManagementStrategies = [...]func(state *pkg.ModuleState) pkg.Concret func(state *pkg.ModuleState) pkg.ConcreteManagementStrategy { return cmis.New(state) }, } +var concreteExtensionStrategies = [...]func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy{ + func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy { + return sff8636.NewSFF8636Extension(state) + }, + func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy { + return sff8636.NewFlexOptixSFF8636Extension(state) + }, +} + var defaultManagementStrategy = func(state *pkg.ModuleState) pkg.ConcreteManagementStrategy { return _default.New(state) } @@ -79,6 +88,7 @@ func main() { module, err := pkg.NewModuleState( defaultManagementStrategy, concreteManagementStrategies[:], + concreteExtensionStrategies[:], handle, ).Get() if err != nil { diff --git a/internal/pkg/optic/sff8636/flexoptix.go b/internal/pkg/optic/sff8636/flexoptix.go index c40351c..a2f131f 100644 --- a/internal/pkg/optic/sff8636/flexoptix.go +++ b/internal/pkg/optic/sff8636/flexoptix.go @@ -1 +1,42 @@ package sff8636 + +import ( + "regexp" + + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" +) + +type FlexOptixSFF8636ManagementStrategy struct { + state *pkg.ModuleState +} + +var ValidFlexOptixVendorName = regexp.MustCompile(`(?i)^flexoptix$`) + +func NewFlexOptixSFF8636Extension(state *pkg.ModuleState) *FlexOptixSFF8636ManagementStrategy { + return &FlexOptixSFF8636ManagementStrategy{ + state: state, + } +} + +func (f FlexOptixSFF8636ManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { + //TODO implement me + return f.state, nil +} + +func (f FlexOptixSFF8636ManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { + //TODO implement me + return f.state, nil +} + +func (f FlexOptixSFF8636ManagementStrategy) ManufacturerIsCompatibleWithProtocolExtension(manufacturer string) bool { + return ValidFlexOptixVendorName.MatchString(manufacturer) +} + +func (f FlexOptixSFF8636ManagementStrategy) SFF8024IsCompatibleWithProtocolExtension(sff8024Identifier byte, sff8024Revision byte) bool { + return checkSFF8024(sff8024Identifier, sff8024Revision) +} + +func (f FlexOptixSFF8636ManagementStrategy) Activate() (*pkg.ModuleState, error) { + f.state.FlexOptixSFF8636Extension.Active = true + return f.state, nil +} diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index 9f9c9c5..489873e 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -12,6 +12,42 @@ type ManagementStrategy struct { state *pkg.ModuleState } +type ExtensionManagementStrategy struct { + state *pkg.ModuleState +} + +func NewSFF8636Extension(state *pkg.ModuleState) *ExtensionManagementStrategy { + return &ExtensionManagementStrategy{ + state: state, + } +} + +func (s2 ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { + // implement SFF8636 specific pages here if needed in the future + return s2.state, nil +} + +func (s2 ExtensionManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { + // implement SFF8636 specific pages here if needed in the future + return s2.state, nil +} + +func (s2 ExtensionManagementStrategy) ManufacturerIsCompatibleWithProtocolExtension(_ string) bool { + return true // manufacturer names have 0 influence, only sff8024 does. so we defer decision +} + +func (s2 ExtensionManagementStrategy) SFF8024IsCompatibleWithProtocolExtension( + sff8024Identifier byte, + sff8024Revision byte, +) bool { + return checkSFF8024(sff8024Identifier, sff8024Revision) +} + +func (s2 ExtensionManagementStrategy) Activate() (*pkg.ModuleState, error) { + s2.state.SFF8636OnlyExtension.Active = true + return s2.state, nil +} + func New(state *pkg.ModuleState) *ManagementStrategy { return &ManagementStrategy{ state: state, @@ -23,6 +59,10 @@ const RefusalVerMismatch = "This module has an " + "as I cannot read nor write to this module without potential failure, data loss and/or equipment damage." func (s2 ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { + return checkSFF8024(sff8024Identifier, sff8024Revision) +} + +func checkSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { var SFF8636CompatibleSFF8024IDs = [...]byte{ 0x0D, // qsfp+ or later with sff8646 or sff8436 mgmt interface 0x11, // qsfp28 or later with sff-8636 management interface diff --git a/internal/pkg/public.go b/internal/pkg/public.go index 9288bed..91ac77c 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -8,18 +8,26 @@ import ( "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick/ssh" ) -// FinIsarState client r/w interface for FinIsar specific settings +// FinIsarCMISExtension client r/w interface for FinIsar specific settings // delegates concrete operations to strategy -type FinIsarState struct { - // handle internal connection handle - handle *connection.I2cRWHandle +type FinIsarCMISExtension struct { + Active bool } -// FlexOptixState client r/w interface for FlexOptix specific settings +// FlexOptixSFF8636Extension client r/w interface for FlexOptix specific settings // delegates concrete operations to strategy -type FlexOptixState struct { - // handle internal connection handle - handle *connection.I2cRWHandle +type FlexOptixSFF8636Extension struct { + Active bool +} + +// CMISOnlyExtension CMIS specific information +type CMISOnlyExtension struct { + Active bool +} + +// SFF8636OnlyExtension SFF8636 specific information +type SFF8636OnlyExtension struct { + Active bool } // ModuleState is data exchange interface - delegates to strategy @@ -27,11 +35,14 @@ type FlexOptixState struct { // manufacturer specific fields can be read / set if struct is present, only one manufacturer // will be present at a time type ModuleState struct { - mgmtProtoConcreteStrategy ConcreteManagementStrategy // private pointer to concrete Strategy - handle *connection.I2cRWHandle // private pointer to connection handle. + mgmtProtoConcreteStrategy ConcreteManagementStrategy // private pointer to concrete Strategy + mgmtProtoExtensionsConcreteStrategies []ConcreteExtensionManagementStrategy + handle *connection.I2cRWHandle // private pointer to connection handle. - FinIsarSpecific *FinIsarState // public read only pointer to FinIsar manufacturer specific info - FlexOptixSpecific *FlexOptixState // public read only pointer to FlexOptix manufacturer specific info + FinIsarCMISExtension FinIsarCMISExtension + FlexOptixSFF8636Extension FlexOptixSFF8636Extension + SFF8636OnlyExtension SFF8636OnlyExtension + CMISOnlyExtension CMISOnlyExtension // lower mem region ManagementProtocol string @@ -53,12 +64,10 @@ type ModuleState struct { func NewModuleState( withDefaultStrategyFactory func(state *ModuleState) ConcreteManagementStrategy, withConcreteStrategiesFactories []func(state *ModuleState) ConcreteManagementStrategy, - withHandle *connection.I2cRWHandle, -) *ModuleState { + withConcreteExtensionStrategiesFactories []func(state *ModuleState) ConcreteExtensionManagementStrategy, + withHandle *connection.I2cRWHandle) *ModuleState { m := &ModuleState{ handle: withHandle, - FinIsarSpecific: nil, - FlexOptixSpecific: nil, ManagementProtocol: "unknown", } m.mgmtProtoConcreteStrategy = withDefaultStrategyFactory(m) // self-referential @@ -78,6 +87,28 @@ func NewModuleState( panic(fmt.Sprintf("Unknown SFF8024 Management interface type %x", m.SFF8024Identifier)) } + // re-query for more advanced admin info + // once we have the concrete management strategy confirmed + m, err = m.GetAdministrativeInformation() + if err != nil { + panic("Failed to query module for basic administrative information") + } + + for _, s := range withConcreteExtensionStrategiesFactories { + extensionStrategy := s(m) + if extensionStrategy.SFF8024IsCompatibleWithProtocolExtension(m.SFF8024Identifier, m.SFF8024Revision) && + extensionStrategy.ManufacturerIsCompatibleWithProtocolExtension(m.VendorName) { + _, err := extensionStrategy.Activate() + if err != nil { + panic("failed to activate protocol extension") + } + m.mgmtProtoExtensionsConcreteStrategies = append( + m.mgmtProtoExtensionsConcreteStrategies, + extensionStrategy, + ) + } + } + return m } @@ -127,6 +158,26 @@ func (m ModuleState) SetTunableLaserCtrlStatus(s *ModuleState) (*ModuleState, er return m.mgmtProtoConcreteStrategy.SetTunableLaserCtrlStatus(s) } +func (m ModuleState) SetExtensionsState(s *ModuleState) (*ModuleState, error) { + for _, e := range m.mgmtProtoExtensionsConcreteStrategies { + _, err := e.SetExtensionState(s) + if err != nil { + return nil, err + } + } + return &m, nil +} + +func (m ModuleState) GetExtensionsState() (*ModuleState, error) { + for _, e := range m.mgmtProtoExtensionsConcreteStrategies { + _, err := e.GetExtensionState() // force refresh + if err != nil { + return nil, err + } + } + return &m, nil +} + // Management is implemented by ModuleState by delegating to strategies which have concrete implementations type Management interface { Set(s *ModuleState) (*ModuleState, error) @@ -137,6 +188,16 @@ type Management interface { SetTunableLaserCtrlStatus(s *ModuleState) (*ModuleState, error) } +// ProtocolExtensionManagement is a generic interface for protocol extensions, +// be it protocol specific or manufacturer specific. I've avoided using Generics for +// the state struct and instead opted to use for an all-containing struct since there's +// no need for the added complexity of Generics in this case. +type ProtocolExtensionManagement interface { + GetExtensionState() (*ModuleState, error) + SetExtensionState(s *ModuleState) (*ModuleState, error) + Activate() (*ModuleState, error) +} + // DirectPageAccess Allows direct read/write access to pages type DirectPageAccess interface { GetPageBin(page byte) ([]byte, error) @@ -149,20 +210,22 @@ type SFF8024Compatible interface { AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool } +type ManufacturerIsCompatibleWithProtocolExtension interface { + ManufacturerIsCompatibleWithProtocolExtension(manufacturer string) bool +} + +type SFF8024IsCompatibleWithProtocolExtension interface { + SFF8024IsCompatibleWithProtocolExtension(sff8024Identifier byte, sff8024Revision byte) bool +} + // ConcreteManagementStrategy should implement both Management and SFF8024Compatible interfaces type ConcreteManagementStrategy interface { Management SFF8024Compatible } -// FlexOptixManagement specific settings for FlexOptix modules. flex tune, nominal wavelength control -type FlexOptixManagement interface { - GetCustomFlexOptixSettings() (*FlexOptixState, error) - SetCustomFlexOptixSettings(s *FlexOptixState) (*FlexOptixState, error) -} - -// FinIsarManagement specific settings for FinIsar modules. TBD. -type FinIsarManagement interface { - GetCustomFinIsarSettings() (*FinIsarState, error) - SetCustomFinIsarSettings(s *FinIsarState) (*FinIsarState, error) +type ConcreteExtensionManagementStrategy interface { + ProtocolExtensionManagement + ManufacturerIsCompatibleWithProtocolExtension + SFF8024IsCompatibleWithProtocolExtension } From 20761f5d045dae24a301d7ddadc8873cd7a08a0d Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Thu, 11 Jun 2026 13:21:08 +0200 Subject: [PATCH 08/36] delete tunable laser ctrl and status was mistakenly included as common between protocols, but doesnt exist in sff8636 as its not standard and has only been backported by a few manufacturers --- internal/pkg/optic/cmis/cmis.go | 10 ---------- internal/pkg/optic/default/default.go | 8 -------- internal/pkg/optic/sff8636/sff8636.go | 10 ---------- internal/pkg/public.go | 15 +++------------ 4 files changed, 3 insertions(+), 40 deletions(-) diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index 1ffd388..70cd88d 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -68,13 +68,3 @@ func (s2 ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (* //TODO implement me panic("implement me") } - -func (s2 ManagementStrategy) GetTunableLaserCtrlStatus() (*pkg.ModuleState, error) { - //TODO implement me - panic("implement me") -} - -func (s2 ManagementStrategy) SetTunableLaserCtrlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { - //TODO implement me - panic("implement me") -} diff --git a/internal/pkg/optic/default/default.go b/internal/pkg/optic/default/default.go index 7cda910..961b379 100644 --- a/internal/pkg/optic/default/default.go +++ b/internal/pkg/optic/default/default.go @@ -45,14 +45,6 @@ func (d ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (*p panic(genericWriteErrorString) } -func (d ManagementStrategy) GetTunableLaserCtrlStatus() (*pkg.ModuleState, error) { - panic(genericReadErrorString) -} - -func (d ManagementStrategy) SetTunableLaserCtrlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { - panic(genericWriteErrorString) -} - var genericWriteErrorString = "Module type is unknown, therefore I cannot go on, " + "and refuse to set any value as it might have undesired effect. " + "Program will be terminated now, as it is likely I failed to detect the Module Type." diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index 489873e..ac7093a 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -144,13 +144,3 @@ func (s2 ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (* //TODO implement me panic("implement me") } - -func (s2 ManagementStrategy) GetTunableLaserCtrlStatus() (*pkg.ModuleState, error) { - //TODO implement me - panic("implement me") -} - -func (s2 ManagementStrategy) SetTunableLaserCtrlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { - //TODO implement me - panic("implement me") -} diff --git a/internal/pkg/public.go b/internal/pkg/public.go index 91ac77c..45d20d1 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -49,12 +49,13 @@ type ModuleState struct { SFF8024Identifier uint8 // SFF8024Identifier lower mem public read-only sff8024 id field SFF8024Revision uint8 // SFF8024Revision lower mem public read-only sff8024 revision id field SoftwareReset bool - EnableHighPowerClass8 bool + EnableHighPowerClass8 bool // TODO check if PowerClasses are present in CMIS too EnableHighPowerClass57 bool LowPwrRequestSW bool LowPwrOverride bool - // page 00 region + // page 00 region, common info between + // CMIS and SFF8636 but addresses differ VendorName string VendorPartNumber string VendorPartRevision string @@ -150,14 +151,6 @@ func (m ModuleState) SetAdministrativeInformation(s *ModuleState) (*ModuleState, return m.mgmtProtoConcreteStrategy.SetAdministrativeInformation(s) } -func (m ModuleState) GetTunableLaserCtrlStatus() (*ModuleState, error) { - return m.mgmtProtoConcreteStrategy.GetTunableLaserCtrlStatus() -} - -func (m ModuleState) SetTunableLaserCtrlStatus(s *ModuleState) (*ModuleState, error) { - return m.mgmtProtoConcreteStrategy.SetTunableLaserCtrlStatus(s) -} - func (m ModuleState) SetExtensionsState(s *ModuleState) (*ModuleState, error) { for _, e := range m.mgmtProtoExtensionsConcreteStrategies { _, err := e.SetExtensionState(s) @@ -184,8 +177,6 @@ type Management interface { Get() (*ModuleState, error) GetAdministrativeInformation() (*ModuleState, error) SetAdministrativeInformation(s *ModuleState) (*ModuleState, error) - GetTunableLaserCtrlStatus() (*ModuleState, error) - SetTunableLaserCtrlStatus(s *ModuleState) (*ModuleState, error) } // ProtocolExtensionManagement is a generic interface for protocol extensions, From a0c863c23ac9353f24c09c43a503f33f35fe322f Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Thu, 11 Jun 2026 15:38:16 +0200 Subject: [PATCH 09/36] refactor GetPageBin and WritePageBin for protocol support + delete write actions - will be reimplemented afterwards --- internal/pkg/optic/cmis/cmis.go | 10 ++ internal/pkg/optic/default/default.go | 17 ++- internal/pkg/optic/sff8636/sff8636.go | 37 +++++- internal/pkg/public.go | 25 ++-- internal/pkg/routines/routines.go | 22 ++-- internal/pkg/routines/write_actions.go | 162 ------------------------- internal/pkg/rtbrick/ssh/connection.go | 27 +---- 7 files changed, 88 insertions(+), 212 deletions(-) delete mode 100644 internal/pkg/routines/write_actions.go diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index 70cd88d..74b4838 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -16,6 +16,16 @@ func New(state *pkg.ModuleState) *ManagementStrategy { } } +func (s2 ManagementStrategy) GetPageBin(page byte, bank byte) ([]byte, error) { + //TODO implement me + panic("implement me") +} + +func (s2 ManagementStrategy) WritePageBin(page byte, bank byte, offset byte, value byte) error { + //TODO implement me + panic("implement me") +} + const RefusalMajorTooHigh string = "This CMIS module has" + " a Major Revision number over what I can speak " + "therefore it is unsupported. Program will be terminated " + diff --git a/internal/pkg/optic/default/default.go b/internal/pkg/optic/default/default.go index 961b379..0cad9e3 100644 --- a/internal/pkg/optic/default/default.go +++ b/internal/pkg/optic/default/default.go @@ -21,6 +21,21 @@ func (d ManagementStrategy) AcceptsSFF8024(_ byte, _ byte) bool { return true } +func (d ManagementStrategy) GetPageBin(_ byte, _ byte) ([]byte, error) { + // we only care about lower mem, we can get it from whatever state + // the module currently is in + handle := d.state.GetHandle() + pageStr, err := handle.Connection.GetI2CDump(handle.I2cBusId) + if err != nil { + return []byte{}, err + } + return pkg.ParseI2CDump(*pageStr), nil +} + +func (d ManagementStrategy) WritePageBin(_ byte, _ byte, _ byte, _ byte) error { + panic(genericWriteErrorString) +} + func (d ManagementStrategy) Set(_ *pkg.ModuleState) (*pkg.ModuleState, error) { panic(genericWriteErrorString) } @@ -30,7 +45,7 @@ func (d ManagementStrategy) Get() (*pkg.ModuleState, error) { } func (d ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { - bin, err := d.state.GetPageBin(0x00) + bin, err := d.state.GetPageBin(0x00, 0) if err != nil { return nil, err } diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index ac7093a..b22905f 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -16,12 +16,47 @@ type ExtensionManagementStrategy struct { state *pkg.ModuleState } +const PageSelectRegisterWriteErrorString = "I could not write to Page Select register, aborting program now." +const PageNotAvailableErrorString = "Module responded that this page is not available, its likely I failed to " + + "correctly identify the module capabilities. Aborting program now." + func NewSFF8636Extension(state *pkg.ModuleState) *ExtensionManagementStrategy { return &ExtensionManagementStrategy{ state: state, } } +func (s2 ManagementStrategy) GetPageBin(page byte, _ byte) ([]byte, error) { + const PageSelectRegisterAddress = 0x7F + handle := s2.state.GetHandle() + + // do raw Page Select write 1st + pageSelectErr := handle.Connection.DoI2CSet(handle.I2cBusId, PageSelectRegisterAddress, page) + if pageSelectErr != nil { + panic(PageSelectRegisterWriteErrorString) + } + + // then get page and decode + pageStr, err := handle.Connection.GetI2CDump(handle.I2cBusId) + if err != nil { + return []byte{}, err + } + dumpBin := pkg.ParseI2CDump(*pageStr) + + // then check Page Select was authorized by reading back Page Select register + if dumpBin[PageSelectRegisterAddress] != page { + panic(PageNotAvailableErrorString) + } + + // return if everything went O.K. + return dumpBin, nil +} + +func (s2 ManagementStrategy) WritePageBin(page byte, bank byte, offset byte, value byte) error { + //TODO implement me + panic("implement me") +} + func (s2 ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { // implement SFF8636 specific pages here if needed in the future return s2.state, nil @@ -116,7 +151,7 @@ func (s2 ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, e const LowPwrRequestSWMask = 0b0000_0010 const LowPwrOverrideMask = 0b0000_0001 - bin, err := s2.state.GetPageBin(0x00) + bin, err := s2.state.GetPageBin(0x00, 0) if err != nil { return nil, err } diff --git a/internal/pkg/public.go b/internal/pkg/public.go index 45d20d1..1f67a8d 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -113,17 +113,16 @@ func NewModuleState( return m } -func (m ModuleState) GetPageBin(page byte) ([]byte, error) { - pageStr, err := m.handle.Connection.GetI2CDump(m.handle.I2cBusId, page) - if err != nil { - return []byte{}, err - } - return ParseI2CDump(*pageStr), nil +func (m ModuleState) GetHandle() *connection.I2cRWHandle { + return m.handle +} + +func (m ModuleState) GetPageBin(page byte, bank byte) ([]byte, error) { + return m.mgmtProtoConcreteStrategy.GetPageBin(page, bank) } -func (m ModuleState) WritePageBin(page byte, offset byte, value byte) error { - // TODO implement me - panic("not implemented.") +func (m ModuleState) WritePageBin(page byte, bank byte, offset byte, value byte) error { + return m.mgmtProtoConcreteStrategy.WritePageBin(page, bank, offset, value) } func (m ModuleState) ToJson() ([]byte, error) { @@ -173,6 +172,8 @@ func (m ModuleState) GetExtensionsState() (*ModuleState, error) { // Management is implemented by ModuleState by delegating to strategies which have concrete implementations type Management interface { + GetPageBin(page byte, bank byte) ([]byte, error) // GetPageBin page is mandatory, bank is optional as it is only used in CMIS + WritePageBin(page byte, bank byte, offset byte, value byte) error // WritePageBin page is mandatory, bank is optional as it is only used in CMIS Set(s *ModuleState) (*ModuleState, error) Get() (*ModuleState, error) GetAdministrativeInformation() (*ModuleState, error) @@ -189,12 +190,6 @@ type ProtocolExtensionManagement interface { Activate() (*ModuleState, error) } -// DirectPageAccess Allows direct read/write access to pages -type DirectPageAccess interface { - GetPageBin(page byte) ([]byte, error) - WritePageBin(page byte, offset byte, value byte) error -} - // SFF8024Compatible to be implemented by concrete strategies type SFF8024Compatible interface { // AcceptsSFF8024 tells if the strategy is compatible with the sff8024 type diff --git a/internal/pkg/routines/routines.go b/internal/pkg/routines/routines.go index 186b71a..301348b 100644 --- a/internal/pkg/routines/routines.go +++ b/internal/pkg/routines/routines.go @@ -37,19 +37,19 @@ func I2cTemplateMethod(actions []I2cAction) cli.ActionFunc { } defer connection.CloseI2CRWHandle(handle) - page00, err := handle.Connection.GetI2CDump(handle.I2cBusId, 0x00) + page00, err := handle.Connection.GetI2CDump(handle.I2cBusId) if err != nil { return err } - page12, err := handle.Connection.GetI2CDump(handle.I2cBusId, 0x12) + page12, err := handle.Connection.GetI2CDump(handle.I2cBusId) if err != nil { return err } - page1E, err := handle.Connection.GetI2CDump(handle.I2cBusId, 0x1E) + page1E, err := handle.Connection.GetI2CDump(handle.I2cBusId) if err != nil { return err } - page1B, err := handle.Connection.GetI2CDump(handle.I2cBusId, 0x1B) + page1B, err := handle.Connection.GetI2CDump(handle.I2cBusId) if err != nil { return err } @@ -90,13 +90,13 @@ var I2CReadAll = I2cTemplateMethod(i2cReadActions[:]) var i2cWriteActions = [...]I2cAction{ ActionShowBasicAdminInfo, // some optics require disabling high power before programming - ActionSetPowerModeTo(optic.PowerModeLowPower), - ActionShowFlexOptixCustomPages, // custom - ActionDisableFlexTune, // custom - ActionSetGridProgramming, // tunable laser - ActionEnableNominalWavelengthControl, // custom - ActionSetPowerClassOverride, // custom - ActionSetPowerMode, + // ActionSetPowerModeTo(optic.PowerModeLowPower), + ActionShowFlexOptixCustomPages, // custom + // ActionDisableFlexTune, // custom + // ActionSetGridProgramming, // tunable laser + // ActionEnableNominalWavelengthControl, // custom + // ActionSetPowerClassOverride, // custom + // ActionSetPowerMode, } var I2CWriteAll = I2cTemplateMethod(i2cWriteActions[:]) diff --git a/internal/pkg/routines/write_actions.go b/internal/pkg/routines/write_actions.go deleted file mode 100644 index daf90d4..0000000 --- a/internal/pkg/routines/write_actions.go +++ /dev/null @@ -1,162 +0,0 @@ -package routines - -import ( - "log/slog" - "strconv" - "time" - - "github.com/pkg/errors" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic" -) - -func ActionSetPowerModeTo(power optic.PowerMode) I2cAction { - powerModeToDescription := map[optic.PowerMode]string{ - optic.PowerModeHighPower: "high", - optic.PowerModeLowPower: "low", - } - return func(args I2cActionArgs) error { - slog.Info("set_power", slog.String("state", powerModeToDescription[power])) - - wPage, wByte, wValue := optic.GetPowerProgramming(power) - err := args.Handle.Connection.DoI2CSet(args.Handle.I2cBusId, wPage, wByte, wValue) - if err != nil { - return err - } - - time.Sleep(1 * time.Second) - - return nil - } -} - -func ActionSetPowerMode(args I2cActionArgs) error { - powerMode, ok := CmdStringToPowerMode[args.Cmd.String("power")] - if !ok { - return errors.New("power mode does not exist") - } - err := ActionSetPowerModeTo(powerMode)(args) - if err != nil { - return err - } - - return nil -} - -func ActionSetPowerClassOverride(args I2cActionArgs) error { - modeStr := args.Cmd.String("power") - powerMode, ok := CmdStringToPowerMode[modeStr] - if !ok { - return errors.New("power mode does not exist") - } - - wPage, wByte, wValue := optic.GetPowerClassProgramming(false) // default low - if powerMode == optic.PowerModeHighPower && args.Page1E.PowerClassOverride != 0x01 { - wPage, wByte, wValue = optic.GetPowerClassProgramming(true) - } else if powerMode == optic.PowerModeLowPower && args.Page1E.PowerClassOverride != 0x00 { - wPage, wByte, wValue = optic.GetPowerClassProgramming(false) - } else { - slog.Info("power_class", slog.String("mode", "already_set")) - return nil - } - - slog.Info("power_class_set", slog.String("mode", modeStr)) - err := args.Handle.Connection.DoI2CSet(args.Handle.I2cBusId, wPage, wByte, wValue) - if err != nil { - return err - } - - time.Sleep(1 * time.Second) - - return nil -} - -func ActionDisableFlexTune(args I2cActionArgs) error { - if args.Page1E.FlexTuneEnabled { - slog.Info("Disabling Flex Tune...") - - wPage, wByte, wValue := optic.GetFlexTuneProgramming() - err := args.Handle.Connection.DoI2CSet(args.Handle.I2cBusId, wPage, wByte, wValue) - if err != nil { - return err - } - - time.Sleep(1 * time.Second) - } else { - slog.Info("Flex Tune is already disabled...") - } - - return nil -} - -func ActionSetGridProgramming(args I2cActionArgs) error { - gridSpacing := args.Cmd.Float64("grid-spacing") - channel := args.Cmd.Int("channel") - - needsGridProgramming := args.Page12.GridDisplay != strconv.FormatFloat(gridSpacing, 'f', 3, 64) - needsChannelProgramming := args.Page12.Channel == nil || *args.Page12.Channel != channel - - if needsGridProgramming { - slog.Info( - "grid spacing mismatch, reprogramming", - slog.Float64("target", gridSpacing), - slog.String("current", args.Page12.GridDisplay), - ) - wPage, wByte, wValue := optic.GetGridProgramming(gridSpacing) - err := args.Handle.Connection.DoI2CSet(args.Handle.I2cBusId, wPage, wByte, wValue) - if err != nil { - return err - } - - } else { - slog.Info( - "grid spacing already matching, will not reprogram", - slog.String("current", args.Page12.GridDisplay), - ) - } - - if needsGridProgramming || needsChannelProgramming { - slog.Info( - "channel mismatch, reprogramming", - slog.Int("target", channel), - slog.Int("current", *args.Page12.Channel), - ) - - wPage, wByte, wValue, wPage2, wByte2, wValue2 := optic.GetChannelProgramming(gridSpacing, channel) - err := args.Handle.Connection.DoI2CSet(args.Handle.I2cBusId, wPage, wByte, wValue) - if err != nil { - return err - } - err = args.Handle.Connection.DoI2CSet(args.Handle.I2cBusId, wPage2, wByte2, wValue2) - if err != nil { - return err - } - - } else { - slog.Info( - "channel already matching, will not reprogram", - slog.Int("current", *args.Page12.Channel), - ) - } - - time.Sleep(1 * time.Second) - - return nil -} - -func ActionEnableNominalWavelengthControl(args I2cActionArgs) error { - if !args.Page1B.NominalWavelengthControlEnabled { - slog.Info("Setting Nominal Wavelength Control Programming...") - - wPage, wByte, wValue := optic.GetNominalWavelengthControlProgramming() - err := args.Handle.Connection.DoI2CSet(args.Handle.I2cBusId, wPage, wByte, wValue) - if err != nil { - return err - } - - time.Sleep(1 * time.Second) - } else { - slog.Info("Nominal Wavelength Control is already enabled...") - } - - return nil -} diff --git a/internal/pkg/rtbrick/ssh/connection.go b/internal/pkg/rtbrick/ssh/connection.go index efde749..a7ed1b9 100644 --- a/internal/pkg/rtbrick/ssh/connection.go +++ b/internal/pkg/rtbrick/ssh/connection.go @@ -2,7 +2,6 @@ package connection import ( "bytes" - "encoding/hex" "encoding/json" "fmt" "io" @@ -155,33 +154,17 @@ func (r *RouterConnection) RunSSHCommand(command string) (string, error) { return stdoutBuffer.String(), nil } -func (r *RouterConnection) GetI2CDump(i2cbusId int, page byte) (*string, error) { - slog.Debug("page_dump_cmd", slog.String("hex_page", hex.EncodeToString([]byte{page}))) - _, err := r.RunSSHCommand(fmt.Sprintf("sudo i2cset -y %d 0x50 127 %d", i2cbusId, page)) - if err != nil { - return nil, err - } - out, err := r.RunSSHCommand(fmt.Sprintf("sudo i2cdump -y %d 0x50 b", i2cbusId)) - if err != nil { - return nil, err - } - _, err = r.RunSSHCommand(fmt.Sprintf("sudo i2cset -y %d 0x50 127 %d", i2cbusId, 0)) +func (r *RouterConnection) GetI2CDump(i2cbusId int) (*string, error) { + slog.Debug("page_dump_cmd", slog.Int("bus_id", i2cbusId)) + out, err := r.RunSSHCommand(fmt.Sprintf("sudo i2cdump -y %d 0x50 i", i2cbusId)) if err != nil { return nil, err } return &out, nil } -func (r *RouterConnection) DoI2CSet(i2cbusId int, page int, byte int, value byte) error { - _, err := r.RunSSHCommand(fmt.Sprintf("sudo i2cset -y %d 0x50 127 %d", i2cbusId, page)) - if err != nil { - return err - } - _, err = r.RunSSHCommand(fmt.Sprintf("sudo i2cset -y %d 0x50 %d %d", i2cbusId, byte, value)) - if err != nil { - return err - } - _, err = r.RunSSHCommand(fmt.Sprintf("sudo i2cset -y %d 0x50 127 %d", i2cbusId, 0)) +func (r *RouterConnection) DoI2CSet(i2cbusId int, offset int, value byte) error { + _, err := r.RunSSHCommand(fmt.Sprintf("sudo i2cset -y %d 0x50 %d %d", i2cbusId, offset, value)) if err != nil { return err } From c56e03c42fcf8aaffba3802fb3abe3b0f66ed582 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Thu, 11 Jun 2026 18:31:21 +0200 Subject: [PATCH 10/36] add flexoptix custom pages (wip) --- internal/pkg/optic/cmis/cmis.go | 135 +++++++++++++++++++ internal/pkg/optic/i2c.go | 15 +-- internal/pkg/optic/sff8636/flexoptix.go | 22 ++- internal/pkg/optic/sff8636/sff8636.go | 26 +++- internal/pkg/optic/util/bin.go | 44 ++++++ internal/pkg/public.go | 171 +++++++++++++++++++++++- 6 files changed, 386 insertions(+), 27 deletions(-) create mode 100644 internal/pkg/optic/util/bin.go diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index 74b4838..f67e956 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -4,8 +4,26 @@ import ( "slices" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" ) +const ( + GridSupported75GhzMask = 0b1000_0000 >> iota + GridSupported33GhzMask + GridSupported100GhzMask + GridSupported50GhzMask + GridSupported25GhzMask + GridSupported12p5GhzMask + GridSupported6p25GhzMask + GridSupported3p125GhzMask +) +const ( + FineTuningSupportedMask = 0b1000_0000 >> iota + GridSupported150GhzMask +) + +const ProgOutputPowerPerLaneSupported = 0b1000_0000 + type ManagementStrategy struct { state *pkg.ModuleState } @@ -78,3 +96,120 @@ func (s2 ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (* //TODO implement me panic("implement me") } + +func (s2 *ManagementStrategy) GetTunableLaserControlStatus(s *pkg.ModuleState) { + // TODO implement me + panic("implement me") +} + +func (s2 *ManagementStrategy) GetLaserCapabilitiesAdvertising(s *pkg.ModuleState) (*pkg.ModuleState, error) { + // TODO implement me + panic("implement me") +} + +// GetTunableLaserControlStatus maxN between 0 and 7 (channel N, used for only-channel-0 access) +func GetTunableLaserControlStatus(s *pkg.ModuleState, bank byte, maxN int) (*pkg.ModuleState, error) { + dumpBin, err := s.GetPageBin(0x12, bank) + if err != nil { + return nil, err + } + caps := &s.FlexOptixSFF8636Extension.TunableLaserCtrlStatus + + for i := 0; i <= maxN; i += 1 { + caps.GridSpacingTx[i] = dumpBin[0x80+i] & pkg.CMISGridSpacingTxMask + caps.GridSpacingTxRO[i] = pkg.CMISGridSpacingToFloatGhzMap[caps.GridSpacingTx[i]] + caps.FineTuningEnableTx[i] = dumpBin[0x80+i]&pkg.CMISFineTuningEnableTxMask != 0 + + caps.ChannelNumberTx[i] = util.ReadBeInt16(dumpBin, 0x88+byte(i)) + caps.FineTuningOffsetTx[i] = util.ReadBeInt16(dumpBin, 0x98+byte(i)) + caps.CurrentLaserFrequencyTx[i] = util.ReadBeUint32(dumpBin, 0xA8+byte(i)) + + caps.TargetOutputPowerTx[i] = util.ReadBeInt16(dumpBin, 0xC8+byte(i)) + + caps.TuningInProgressTx[i] = dumpBin[0xDE+i]&pkg.CMISTuningInProgressTxMask != 0 + caps.WaveLengthUnlockStatus[i] = dumpBin[0xDE+i]&pkg.CMISWavelengthUnlockStatusTxMask != 0 + + // per spec: the bit n-1 is set if and only if any of the latched flags are set to 1 + caps.LaserTuningFlagSummaryTx[i] = (dumpBin[0xE6+i] & (0b0000_0001 << i)) != 0 + + caps.TargetOutputPowerOORFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISTargetOutputPowerOORFlagTxMask != 0 + caps.FineTuningOutOfRangeFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISFineTuningOutOfRangeFlagTxMask != 0 + caps.TuningNotAcceptedMaskTx[i] = dumpBin[0xE7+i]&pkg.CMISTuningNotAcceptedFlagTxMask != 0 + caps.InvalidChannelNumberFLagTx[i] = dumpBin[0xE7+i]&pkg.CMISInvalidChannelNumberFlagTxMask != 0 + caps.WavelengthUnlockedFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISWavelengthUnlockedFlagTxMask != 0 + caps.TuningCompleteFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISTuningCompleteFlagTxMask != 0 + + // reading interrupt masks, bitfield pattern same as alarm + caps.TargetOutputPowerOORMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISTargetOutputPowerOORFlagTxMask != 0 + caps.FineTuningPowerOutOfRangeMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISFineTuningOutOfRangeFlagTxMask != 0 + caps.TuningNotAcceptedMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISTuningNotAcceptedFlagTxMask != 0 + caps.InvalidChannelMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISInvalidChannelNumberFlagTxMask != 0 + caps.WavelengthUnlockedMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISWavelengthUnlockedFlagTxMask != 0 + caps.TuningCompleteMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISTuningCompleteFlagTxMask != 0 + } + + return s, nil +} + +func GetLaserCapabilitiesAdvertising(s *pkg.ModuleState, bank byte) (*pkg.ModuleState, error) { + dumpBin, err := s.GetPageBin(0x04, bank) + if err != nil { + return nil, err + } + caps := &s.FlexOptixSFF8636Extension.LaserCapabilities + + // I really hate that Go doesn't have bitfields. + caps.GridSupported75Ghz = dumpBin[0x80]&GridSupported75GhzMask != 0 + caps.GridSupported33Ghz = dumpBin[0x80]&GridSupported33GhzMask != 0 + caps.GridSupported100Ghz = dumpBin[0x80]&GridSupported100GhzMask != 0 + caps.GridSupported50Ghz = dumpBin[0x80]&GridSupported50GhzMask != 0 + caps.GridSupported25Ghz = dumpBin[0x80]&GridSupported25GhzMask != 0 + caps.GridSupported12p5Ghz = dumpBin[0x80]&GridSupported12p5GhzMask != 0 + caps.GridSupported6p25Ghz = dumpBin[0x80]&GridSupported6p25GhzMask != 0 + caps.GridSupported3p125Ghz = dumpBin[0x80]&GridSupported3p125GhzMask != 0 + + caps.FineTuningSupported = dumpBin[0x81]&FineTuningSupportedMask != 0 + caps.GridSupported150Ghz = dumpBin[0x81]&GridSupported150GhzMask != 0 + + var base byte = 0x82 + // 3.125Ghz + caps.GridLowChannel3p125Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel3p125Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 6.25Ghz + caps.GridLowChannel6p25Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel6p25Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 12.5Ghz + caps.GridLowChannel12p5Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel12p5Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 25Ghz + caps.GridLowChannel25Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel25Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 50Ghz + caps.GridLowChannel50Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel50Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 100Ghz + caps.GridLowChannel100Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel100Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 33Ghz + caps.GridLowChannel33Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel33Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 75Ghz + caps.GridLowChannel75Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel75Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 150Ghz + caps.GridLowChannel150Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel150Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + + base = 0xBE // skip reserved region + caps.FineTuningResolution = util.ReadBeUint16AndShiftBase(dumpBin, &base) + caps.FineTuningLowOffset = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.FineTuningHighOffset = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.ProgOutputPowerPerLaneSupported = dumpBin[base]&ProgOutputPowerPerLaneSupported != 0 + + base = 0xC6 // skip reserved region + caps.ProgOutputPowerMin = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.ProgOutputPowerMax = util.ReadBeInt16AndShiftBase(dumpBin, &base) + + // TODO checksum support + return s, nil +} diff --git a/internal/pkg/optic/i2c.go b/internal/pkg/optic/i2c.go index 6144751..b1e1dde 100644 --- a/internal/pkg/optic/i2c.go +++ b/internal/pkg/optic/i2c.go @@ -2,7 +2,6 @@ package optic import ( "encoding/binary" - "math" "strconv" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" @@ -17,18 +16,6 @@ func keysByValue[K comparable, V comparable](m map[K]V, value V) *K { return nil } -func TwoComplement16(sizeBits uint8, data uint16) (v int16) { - n := float64(sizeBits - 1) - p := math.Pow(2, n) // 2^(N-1) - mask := uint16(p) - - tmp1 := -int16(data & mask) - tmp2 := int16(data & ^mask) - - v = tmp1 + tmp2 - return v -} - func ToTwoComplement16(a int16) []byte { b := make([]byte, 2) binary.LittleEndian.PutUint16(b, uint16(a)) @@ -125,7 +112,7 @@ func InterpretPage12(dump []byte) I2CPage12 { gridSetting := I2CPage12GridMap.get(bitfieldGrid) u := binary.BigEndian.Uint16(dump[136:138]) - frequencyOffset := int(TwoComplement16(16, u)) + frequencyOffset := int(util.TwoComplement16(16, u)) gridMultiplier := I2CPage12GridMultiplierMap[gridSetting] opticFrequency := DWDMCenterFreqHz + (frequencyOffset * gridMultiplier) diff --git a/internal/pkg/optic/sff8636/flexoptix.go b/internal/pkg/optic/sff8636/flexoptix.go index a2f131f..c0b26c3 100644 --- a/internal/pkg/optic/sff8636/flexoptix.go +++ b/internal/pkg/optic/sff8636/flexoptix.go @@ -4,6 +4,7 @@ import ( "regexp" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/cmis" ) type FlexOptixSFF8636ManagementStrategy struct { @@ -18,25 +19,34 @@ func NewFlexOptixSFF8636Extension(state *pkg.ModuleState) *FlexOptixSFF8636Manag } } -func (f FlexOptixSFF8636ManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { - //TODO implement me +func (f *FlexOptixSFF8636ManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { + _, err := cmis.GetLaserCapabilitiesAdvertising(f.state, 0x00) + if err != nil { + return nil, err + } + + _, err = cmis.GetTunableLaserControlStatus(f.state, 0x00, 0) + if err != nil { + return nil, err + } + return f.state, nil } -func (f FlexOptixSFF8636ManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (f *FlexOptixSFF8636ManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { //TODO implement me return f.state, nil } -func (f FlexOptixSFF8636ManagementStrategy) ManufacturerIsCompatibleWithProtocolExtension(manufacturer string) bool { +func (f *FlexOptixSFF8636ManagementStrategy) ManufacturerIsCompatibleWithProtocolExtension(manufacturer string) bool { return ValidFlexOptixVendorName.MatchString(manufacturer) } -func (f FlexOptixSFF8636ManagementStrategy) SFF8024IsCompatibleWithProtocolExtension(sff8024Identifier byte, sff8024Revision byte) bool { +func (f *FlexOptixSFF8636ManagementStrategy) SFF8024IsCompatibleWithProtocolExtension(sff8024Identifier byte, sff8024Revision byte) bool { return checkSFF8024(sff8024Identifier, sff8024Revision) } -func (f FlexOptixSFF8636ManagementStrategy) Activate() (*pkg.ModuleState, error) { +func (f *FlexOptixSFF8636ManagementStrategy) Activate() (*pkg.ModuleState, error) { f.state.FlexOptixSFF8636Extension.Active = true return f.state, nil } diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index b22905f..e300987 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -1,6 +1,7 @@ package sff8636 import ( + "fmt" "slices" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" @@ -16,8 +17,10 @@ type ExtensionManagementStrategy struct { state *pkg.ModuleState } +const PageSelectRegisterAddress = 0x7F const PageSelectRegisterWriteErrorString = "I could not write to Page Select register, aborting program now." -const PageNotAvailableErrorString = "Module responded that this page is not available, its likely I failed to " + +const RegisterWriteErrorString = "I could not write to arbitrary register, aborting program now." +const PageNotAvailableTemplateErrorString = "Module responded that page 0x%x is not available, its likely I failed to " + "correctly identify the module capabilities. Aborting program now." func NewSFF8636Extension(state *pkg.ModuleState) *ExtensionManagementStrategy { @@ -27,7 +30,6 @@ func NewSFF8636Extension(state *pkg.ModuleState) *ExtensionManagementStrategy { } func (s2 ManagementStrategy) GetPageBin(page byte, _ byte) ([]byte, error) { - const PageSelectRegisterAddress = 0x7F handle := s2.state.GetHandle() // do raw Page Select write 1st @@ -45,16 +47,28 @@ func (s2 ManagementStrategy) GetPageBin(page byte, _ byte) ([]byte, error) { // then check Page Select was authorized by reading back Page Select register if dumpBin[PageSelectRegisterAddress] != page { - panic(PageNotAvailableErrorString) + panic(fmt.Sprintf(PageNotAvailableTemplateErrorString, page)) } // return if everything went O.K. return dumpBin, nil } -func (s2 ManagementStrategy) WritePageBin(page byte, bank byte, offset byte, value byte) error { - //TODO implement me - panic("implement me") +func (s2 ManagementStrategy) WritePageBin(page byte, _ byte, offset byte, value byte) error { + handle := s2.state.GetHandle() + + // do raw Page Select Write 1st + pageSelectErr := handle.Connection.DoI2CSet(handle.I2cBusId, PageSelectRegisterAddress, page) + if pageSelectErr != nil { + panic(PageSelectRegisterWriteErrorString) + } + + err := handle.Connection.DoI2CSet(handle.I2cBusId, (int)(offset), value) + if err != nil { + panic(RegisterWriteErrorString) + } + + return nil } func (s2 ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { diff --git a/internal/pkg/optic/util/bin.go b/internal/pkg/optic/util/bin.go new file mode 100644 index 0000000..b31bb50 --- /dev/null +++ b/internal/pkg/optic/util/bin.go @@ -0,0 +1,44 @@ +package util + +import ( + "encoding/binary" + "math" +) + +func TwoComplement16(sizeBits uint8, data uint16) (v int16) { + n := float64(sizeBits - 1) + p := math.Pow(2, n) // 2^(N-1) + mask := uint16(p) + + tmp1 := -int16(data & mask) + tmp2 := int16(data & ^mask) + + v = tmp1 + tmp2 + return v +} + +func ReadBeUint16(bin []byte, baseOffset byte) uint16 { + // CMIS states that the default endianness for numerical data types is big endian + // unless stated otherwise + return binary.BigEndian.Uint16(bin[baseOffset : baseOffset+0x02]) // go slices use half-open range, like this: [a,b[ +} + +func ReadBeUint32(bin []byte, baseOffset byte) uint32 { + return binary.BigEndian.Uint32(bin[baseOffset : baseOffset+0x04]) // go slices use half-open ranges +} + +func ReadBeInt16(bin []byte, baseOffset byte) int16 { + return TwoComplement16(16, ReadBeUint16(bin, baseOffset)) +} + +func ReadBeInt16AndShiftBase(bin []byte, baseOffset *byte) int16 { + i := ReadBeInt16(bin, *baseOffset) + *baseOffset += 0x02 + return i +} + +func ReadBeUint16AndShiftBase(bin []byte, baseOffset *byte) uint16 { + i := ReadBeUint16(bin, *baseOffset) + *baseOffset += 0x02 + return i +} diff --git a/internal/pkg/public.go b/internal/pkg/public.go index 1f67a8d..5a4ac13 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -18,11 +18,172 @@ type FinIsarCMISExtension struct { // delegates concrete operations to strategy type FlexOptixSFF8636Extension struct { Active bool + + // FlexOptix has page 04h and 12h copied from CMIS + LaserCapabilities CMISLaserCapabilitiesAdvertising + TunableLaserCtrlStatus CMISBankedTunableLaserControlAndStatus // n=1 only, up to media 8 lanes support } // CMISOnlyExtension CMIS specific information type CMISOnlyExtension struct { Active bool + + // TODO implement + // SupportedControls CMISSupportedControlsAdvertising + // LaserCapabilities CMISLaserCapabilitiesAdvertising + // TunableLaserCtrlStatus [4]CMISBankedTunableLaserControlAndStatus // CMIS 5.3 defines 4 banks max. +} + +type CMISSupportedControlsAdvertising struct { + // page 01h supported controls adv + WavelengthIsControllable bool + TransmitterIsTunable bool + SquelchMethodTx byte + ForcedSquelchTxSupported bool + AutoSquelchDisableTxSupported bool + AutoSquelchDisableRxSupported bool + OutputDisableTxSupported bool + OutputDisableRxSupported bool + InputPolarityFlipTxSupported bool + OutputPolarityFlipRxSupported bool + BankBroadcastSupported bool +} +type CMISLaserCapabilitiesAdvertising struct { + // page 04h laser capabilities adv + // is channel-based grid tuning supported on these frequencies + GridSupported3p125Ghz bool + GridSupported6p25Ghz bool + GridSupported12p5Ghz bool + GridSupported25Ghz bool + GridSupported33Ghz bool + GridSupported50Ghz bool + GridSupported75Ghz bool + GridSupported100Ghz bool + GridSupported150Ghz bool + + // is fine-tuning supported in the vicinity of an on-grid channel + FineTuningSupported bool + + // S16 encoded lowest N for spacing for each freq + GridLowChannel3p125Ghz int16 + GridLowChannel6p25Ghz int16 + GridLowChannel12p5Ghz int16 + GridLowChannel25Ghz int16 + GridLowChannel33Ghz int16 + GridLowChannel50Ghz int16 + GridLowChannel100Ghz int16 + GridLowChannel75Ghz int16 + GridLowChannel150Ghz int16 + + // S16 encoded higher N for spacing for each freq + GridHighChannel3p125Ghz int16 + GridHighChannel6p25Ghz int16 + GridHighChannel12p5Ghz int16 + GridHighChannel25Ghz int16 + GridHighChannel33Ghz int16 + GridHighChannel50Ghz int16 + GridHighChannel100Ghz int16 + GridHighChannel75Ghz int16 + GridHighChannel150Ghz int16 + + // fine-tuning res, 0.001 Ghz increments + FineTuningResolution uint16 + FineTuningLowOffset int16 + FineTuningHighOffset int16 + + // programmable output power + ProgOutputPowerPerLaneSupported bool // per-lane programmable y/n + ProgOutputPowerMin int16 // 0.001 dBm increments min power + ProgOutputPowerMax int16 // 0.001 dBm increments max power +} + +const ( + CMISGridSpacing3p125Ghz = 0b0000 + CMISGridSpacing6p25Ghz = 0b0001 + CMISGridSpacing12p5Ghz = 0b0010 + CMISGridSpacing25Ghz = 0b0011 + CMISGridSpacing50Ghz = 0b0100 + CMISGridSpacing100Ghz = 0b0101 + CMISGridSpacing33Ghz = 0b0110 + CMISGridSpacing75Ghz = 0b0111 + CMISGridSpacing150Ghz = 0b1000 + CMISGridSpacingNotAvailable = 0b1111 +) + +var CMISGridSpacingToFloatGhzMap = map[byte]float64{ + CMISGridSpacing3p125Ghz: 3.125, + CMISGridSpacing6p25Ghz: 6.25, + CMISGridSpacing12p5Ghz: 12.5, + CMISGridSpacing25Ghz: 25.0, + CMISGridSpacing50Ghz: 50.0, + CMISGridSpacing100Ghz: 100.0, + CMISGridSpacing33Ghz: 33.0, + CMISGridSpacing75Ghz: 75.0, + CMISGridSpacing150Ghz: 150.0, +} + +var FloatGhzToCMISGridSpacing = map[float64]int8{ + 3.125: CMISGridSpacing3p125Ghz, + 6.25: CMISGridSpacing6p25Ghz, + 12.5: CMISGridSpacing12p5Ghz, + 25.0: CMISGridSpacing25Ghz, + 50.0: CMISGridSpacing50Ghz, + 100.0: CMISGridSpacing100Ghz, + 33.0: CMISGridSpacing33Ghz, + 75.0: CMISGridSpacing75Ghz, + 150.0: CMISGridSpacing150Ghz, +} + +const ( + CMISGridSpacingTxMask = 0b1111_0000 + CMISFineTuningEnableTxMask = 0b0000_0001 + CMISTuningInProgressTxMask = 0b0000_0010 + CMISWavelengthUnlockStatusTxMask = 0b0000_0001 + + CMISTargetOutputPowerOORFlagTxMask = 0b0010_0000 + CMISFineTuningOutOfRangeFlagTxMask = 0b0001_0000 + CMISTuningNotAcceptedFlagTxMask = 0b0000_1000 + CMISInvalidChannelNumberFlagTxMask = 0b0000_0100 + CMISWavelengthUnlockedFlagTxMask = 0b0000_0010 + CMISTuningCompleteFlagTxMask = 0b0000_0001 +) + +type CMISBankedTunableLaserControlAndStatus struct { + // page 12h tunable laser control and status + + // Grid + GridSpacingTx [8]byte // selected grid spacing of media lanes 1-8 OF BANK + GridSpacingTxRO [8]float64 // read only float64 ghz value + FineTuningEnableTx [8]bool // for each lane + + // tuning and status + ChannelNumberTx [8]int16 // S16 selected N - channel number for media lane 1-8 OF BANK + FineTuningOffsetTx [8]int16 // S16 fine-tuning frequency offset for media lane 1-8 OF BANK in offsets of 0.001 Ghz + CurrentLaserFrequencyTx [8]uint32 // U32 current frequency for media lane 1-8 OF BANK in units of 0.001 Ghz + + // power + TargetOutputPowerTx [8]int16 // s16 programmable output power for all media lanes IN BANK units of 0.01dBm + + // lock status + TuningInProgressTx [8]bool // whether tuning is in progress on all media lanes IN BANK + WaveLengthUnlockStatus [8]bool // unlocked status indication for laser on all media lanes IN BANK + + // latched flags, cleared on module read + LaserTuningFlagSummaryTx [8]bool + TargetOutputPowerOORFlagTx [8]bool // indicates whether target output power value was entered for media lane + FineTuningOutOfRangeFlagTx [8]bool // indicates whether fine-tuning target value was outside range + TuningNotAcceptedFlagTx [8]bool // indicates a failed tuning operation for media lane + InvalidChannelNumberFLagTx [8]bool // required channel number not in advertised range of spacing + WavelengthUnlockedFlagTx [8]bool + TuningCompleteFlagTx [8]bool // tuning has been completed y/n + + // masks for interrupt generation suppression + TargetOutputPowerOORMaskTx [8]bool + FineTuningPowerOutOfRangeMaskTx [8]bool + TuningNotAcceptedMaskTx [8]bool + InvalidChannelMaskTx [8]bool + WavelengthUnlockedMaskTx [8]bool + TuningCompleteMaskTx [8]bool } // SFF8636OnlyExtension SFF8636 specific information @@ -139,7 +300,15 @@ func (m ModuleState) Set(s *ModuleState) (*ModuleState, error) { } func (m ModuleState) Get() (*ModuleState, error) { - return m.mgmtProtoConcreteStrategy.Get() + _, err := m.mgmtProtoConcreteStrategy.Get() + if err != nil { + return nil, err + } + _, err = m.GetExtensionsState() + if err != nil { + return nil, err + } + return &m, nil } func (m ModuleState) GetAdministrativeInformation() (*ModuleState, error) { From 1ffdd3dbe85be2db1ae101b7e5cf5a1cf26ba468 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Thu, 11 Jun 2026 18:45:11 +0200 Subject: [PATCH 11/36] switch to pointer receiver --- internal/pkg/optic/cmis/cmis.go | 14 +++++++------- internal/pkg/optic/default/default.go | 14 +++++++------- internal/pkg/optic/sff8636/sff8636.go | 24 ++++++++++++------------ internal/pkg/public.go | 26 +++++++++++++------------- 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index f67e956..a63620f 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -34,12 +34,12 @@ func New(state *pkg.ModuleState) *ManagementStrategy { } } -func (s2 ManagementStrategy) GetPageBin(page byte, bank byte) ([]byte, error) { +func (s2 *ManagementStrategy) GetPageBin(page byte, bank byte) ([]byte, error) { //TODO implement me panic("implement me") } -func (s2 ManagementStrategy) WritePageBin(page byte, bank byte, offset byte, value byte) error { +func (s2 *ManagementStrategy) WritePageBin(page byte, bank byte, offset byte, value byte) error { //TODO implement me panic("implement me") } @@ -50,7 +50,7 @@ const RefusalMajorTooHigh string = "This CMIS module has" + "now, as I cannot read nor write to this module without " + "potential failure, data loss and/or equipment damage." -func (s2 ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { +func (s2 *ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { var CmisCompatibleSFF8024IDs = [...]byte{ 0x1E, // qsfp+ or later with cmis 0x1F, // sfp-dd with cmis @@ -77,22 +77,22 @@ func (s2 ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revis return compatibleIdentifier(sff8024Identifier) } -func (s2 ManagementStrategy) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } -func (s2 ManagementStrategy) Get() (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) Get() (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } -func (s2 ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } -func (s2 ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } diff --git a/internal/pkg/optic/default/default.go b/internal/pkg/optic/default/default.go index 0cad9e3..466daa0 100644 --- a/internal/pkg/optic/default/default.go +++ b/internal/pkg/optic/default/default.go @@ -17,11 +17,11 @@ func New(state *pkg.ModuleState) *ManagementStrategy { } } -func (d ManagementStrategy) AcceptsSFF8024(_ byte, _ byte) bool { +func (d *ManagementStrategy) AcceptsSFF8024(_ byte, _ byte) bool { return true } -func (d ManagementStrategy) GetPageBin(_ byte, _ byte) ([]byte, error) { +func (d *ManagementStrategy) GetPageBin(_ byte, _ byte) ([]byte, error) { // we only care about lower mem, we can get it from whatever state // the module currently is in handle := d.state.GetHandle() @@ -32,19 +32,19 @@ func (d ManagementStrategy) GetPageBin(_ byte, _ byte) ([]byte, error) { return pkg.ParseI2CDump(*pageStr), nil } -func (d ManagementStrategy) WritePageBin(_ byte, _ byte, _ byte, _ byte) error { +func (d *ManagementStrategy) WritePageBin(_ byte, _ byte, _ byte, _ byte) error { panic(genericWriteErrorString) } -func (d ManagementStrategy) Set(_ *pkg.ModuleState) (*pkg.ModuleState, error) { +func (d *ManagementStrategy) Set(_ *pkg.ModuleState) (*pkg.ModuleState, error) { panic(genericWriteErrorString) } -func (d ManagementStrategy) Get() (*pkg.ModuleState, error) { +func (d *ManagementStrategy) Get() (*pkg.ModuleState, error) { panic(genericReadErrorString) } -func (d ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { +func (d *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { bin, err := d.state.GetPageBin(0x00, 0) if err != nil { return nil, err @@ -56,7 +56,7 @@ func (d ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, er return d.state, nil } -func (d ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (d *ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { panic(genericWriteErrorString) } diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index e300987..831af2a 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -29,7 +29,7 @@ func NewSFF8636Extension(state *pkg.ModuleState) *ExtensionManagementStrategy { } } -func (s2 ManagementStrategy) GetPageBin(page byte, _ byte) ([]byte, error) { +func (s2 *ManagementStrategy) GetPageBin(page byte, _ byte) ([]byte, error) { handle := s2.state.GetHandle() // do raw Page Select write 1st @@ -54,7 +54,7 @@ func (s2 ManagementStrategy) GetPageBin(page byte, _ byte) ([]byte, error) { return dumpBin, nil } -func (s2 ManagementStrategy) WritePageBin(page byte, _ byte, offset byte, value byte) error { +func (s2 *ManagementStrategy) WritePageBin(page byte, _ byte, offset byte, value byte) error { handle := s2.state.GetHandle() // do raw Page Select Write 1st @@ -71,28 +71,28 @@ func (s2 ManagementStrategy) WritePageBin(page byte, _ byte, offset byte, value return nil } -func (s2 ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { +func (s2 *ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { // implement SFF8636 specific pages here if needed in the future return s2.state, nil } -func (s2 ExtensionManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 *ExtensionManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { // implement SFF8636 specific pages here if needed in the future return s2.state, nil } -func (s2 ExtensionManagementStrategy) ManufacturerIsCompatibleWithProtocolExtension(_ string) bool { +func (s2 *ExtensionManagementStrategy) ManufacturerIsCompatibleWithProtocolExtension(_ string) bool { return true // manufacturer names have 0 influence, only sff8024 does. so we defer decision } -func (s2 ExtensionManagementStrategy) SFF8024IsCompatibleWithProtocolExtension( +func (s2 *ExtensionManagementStrategy) SFF8024IsCompatibleWithProtocolExtension( sff8024Identifier byte, sff8024Revision byte, ) bool { return checkSFF8024(sff8024Identifier, sff8024Revision) } -func (s2 ExtensionManagementStrategy) Activate() (*pkg.ModuleState, error) { +func (s2 *ExtensionManagementStrategy) Activate() (*pkg.ModuleState, error) { s2.state.SFF8636OnlyExtension.Active = true return s2.state, nil } @@ -107,7 +107,7 @@ const RefusalVerMismatch = "This module has an " + "SFF8636 Revision number that I do not know of, therefore it is unsupported. Program will be terminated now, " + "as I cannot read nor write to this module without potential failure, data loss and/or equipment damage." -func (s2 ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { +func (s2 *ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { return checkSFF8024(sff8024Identifier, sff8024Revision) } @@ -143,12 +143,12 @@ func checkSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { return compatibleIdentifier(sff8024Identifier) } -func (s2 ManagementStrategy) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } -func (s2 ManagementStrategy) Get() (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) Get() (*pkg.ModuleState, error) { _, err := s2.GetAdministrativeInformation() if err != nil { return nil, err @@ -157,7 +157,7 @@ func (s2 ManagementStrategy) Get() (*pkg.ModuleState, error) { return s2.state, nil } -func (s2 ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { // register 0x5D lower mem masks const SoftwareResetMask = 0b1000_0000 const EnableHighPowerClass8Mask = 0b0000_1000 @@ -189,7 +189,7 @@ func (s2 ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, e return s2.state, nil } -func (s2 ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } diff --git a/internal/pkg/public.go b/internal/pkg/public.go index 5a4ac13..4eec056 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -274,19 +274,19 @@ func NewModuleState( return m } -func (m ModuleState) GetHandle() *connection.I2cRWHandle { +func (m *ModuleState) GetHandle() *connection.I2cRWHandle { return m.handle } -func (m ModuleState) GetPageBin(page byte, bank byte) ([]byte, error) { +func (m *ModuleState) GetPageBin(page byte, bank byte) ([]byte, error) { return m.mgmtProtoConcreteStrategy.GetPageBin(page, bank) } -func (m ModuleState) WritePageBin(page byte, bank byte, offset byte, value byte) error { +func (m *ModuleState) WritePageBin(page byte, bank byte, offset byte, value byte) error { return m.mgmtProtoConcreteStrategy.WritePageBin(page, bank, offset, value) } -func (m ModuleState) ToJson() ([]byte, error) { +func (m *ModuleState) ToJson() ([]byte, error) { marshal, err := json.MarshalIndent(m, "", "\t") if err != nil { return nil, err @@ -295,11 +295,11 @@ func (m ModuleState) ToJson() ([]byte, error) { return marshal, nil } -func (m ModuleState) Set(s *ModuleState) (*ModuleState, error) { +func (m *ModuleState) Set(s *ModuleState) (*ModuleState, error) { return m.mgmtProtoConcreteStrategy.Set(s) } -func (m ModuleState) Get() (*ModuleState, error) { +func (m *ModuleState) Get() (*ModuleState, error) { _, err := m.mgmtProtoConcreteStrategy.Get() if err != nil { return nil, err @@ -308,35 +308,35 @@ func (m ModuleState) Get() (*ModuleState, error) { if err != nil { return nil, err } - return &m, nil + return m, nil } -func (m ModuleState) GetAdministrativeInformation() (*ModuleState, error) { +func (m *ModuleState) GetAdministrativeInformation() (*ModuleState, error) { return m.mgmtProtoConcreteStrategy.GetAdministrativeInformation() } -func (m ModuleState) SetAdministrativeInformation(s *ModuleState) (*ModuleState, error) { +func (m *ModuleState) SetAdministrativeInformation(s *ModuleState) (*ModuleState, error) { return m.mgmtProtoConcreteStrategy.SetAdministrativeInformation(s) } -func (m ModuleState) SetExtensionsState(s *ModuleState) (*ModuleState, error) { +func (m *ModuleState) SetExtensionsState(s *ModuleState) (*ModuleState, error) { for _, e := range m.mgmtProtoExtensionsConcreteStrategies { _, err := e.SetExtensionState(s) if err != nil { return nil, err } } - return &m, nil + return m, nil } -func (m ModuleState) GetExtensionsState() (*ModuleState, error) { +func (m *ModuleState) GetExtensionsState() (*ModuleState, error) { for _, e := range m.mgmtProtoExtensionsConcreteStrategies { _, err := e.GetExtensionState() // force refresh if err != nil { return nil, err } } - return &m, nil + return m, nil } // Management is implemented by ModuleState by delegating to strategies which have concrete implementations From 3c38074c14999676ffa41d2aa4274932c87428ab Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 17 Jun 2026 15:24:24 +0200 Subject: [PATCH 12/36] rename WritePageBin to WritePageByteBin --- internal/pkg/optic/cmis/cmis.go | 2 +- internal/pkg/optic/default/default.go | 2 +- internal/pkg/optic/sff8636/sff8636.go | 2 +- internal/pkg/public.go | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index a63620f..7feff5f 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -39,7 +39,7 @@ func (s2 *ManagementStrategy) GetPageBin(page byte, bank byte) ([]byte, error) { panic("implement me") } -func (s2 *ManagementStrategy) WritePageBin(page byte, bank byte, offset byte, value byte) error { +func (s2 *ManagementStrategy) WritePageByteBin(page byte, bank byte, offset byte, value byte) error { //TODO implement me panic("implement me") } diff --git a/internal/pkg/optic/default/default.go b/internal/pkg/optic/default/default.go index 466daa0..0131378 100644 --- a/internal/pkg/optic/default/default.go +++ b/internal/pkg/optic/default/default.go @@ -32,7 +32,7 @@ func (d *ManagementStrategy) GetPageBin(_ byte, _ byte) ([]byte, error) { return pkg.ParseI2CDump(*pageStr), nil } -func (d *ManagementStrategy) WritePageBin(_ byte, _ byte, _ byte, _ byte) error { +func (d *ManagementStrategy) WritePageByteBin(_ byte, _ byte, _ byte, _ byte) error { panic(genericWriteErrorString) } diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index 831af2a..b245518 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -54,7 +54,7 @@ func (s2 *ManagementStrategy) GetPageBin(page byte, _ byte) ([]byte, error) { return dumpBin, nil } -func (s2 *ManagementStrategy) WritePageBin(page byte, _ byte, offset byte, value byte) error { +func (s2 *ManagementStrategy) WritePageByteBin(page byte, _ byte, offset byte, value byte) error { handle := s2.state.GetHandle() // do raw Page Select Write 1st diff --git a/internal/pkg/public.go b/internal/pkg/public.go index 4eec056..0e0984d 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -282,8 +282,8 @@ func (m *ModuleState) GetPageBin(page byte, bank byte) ([]byte, error) { return m.mgmtProtoConcreteStrategy.GetPageBin(page, bank) } -func (m *ModuleState) WritePageBin(page byte, bank byte, offset byte, value byte) error { - return m.mgmtProtoConcreteStrategy.WritePageBin(page, bank, offset, value) +func (m *ModuleState) WritePageByteBin(page byte, bank byte, offset byte, value byte) error { + return m.mgmtProtoConcreteStrategy.WritePageByteBin(page, bank, offset, value) } func (m *ModuleState) ToJson() ([]byte, error) { @@ -341,8 +341,8 @@ func (m *ModuleState) GetExtensionsState() (*ModuleState, error) { // Management is implemented by ModuleState by delegating to strategies which have concrete implementations type Management interface { - GetPageBin(page byte, bank byte) ([]byte, error) // GetPageBin page is mandatory, bank is optional as it is only used in CMIS - WritePageBin(page byte, bank byte, offset byte, value byte) error // WritePageBin page is mandatory, bank is optional as it is only used in CMIS + GetPageBin(page byte, bank byte) ([]byte, error) // GetPageBin page is mandatory, bank is optional as it is only used in CMIS + WritePageByteBin(page byte, bank byte, offset byte, value byte) error // WritePageByteBin page is mandatory, bank is optional as it is only used in CMIS Set(s *ModuleState) (*ModuleState, error) Get() (*ModuleState, error) GetAdministrativeInformation() (*ModuleState, error) From a599e2e9dbddfed3dd34650899cb8538c52bbeb9 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 17 Jun 2026 16:01:08 +0200 Subject: [PATCH 13/36] add diff iterator + WritePageBin method for writing whole pages --- internal/pkg/optic/util/bin.go | 16 ++++++++++++++++ internal/pkg/public.go | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/internal/pkg/optic/util/bin.go b/internal/pkg/optic/util/bin.go index b31bb50..9cee083 100644 --- a/internal/pkg/optic/util/bin.go +++ b/internal/pkg/optic/util/bin.go @@ -2,6 +2,7 @@ package util import ( "encoding/binary" + "iter" "math" ) @@ -42,3 +43,18 @@ func ReadBeUint16AndShiftBase(bin []byte, baseOffset *byte) uint16 { *baseOffset += 0x02 return i } + +// BinDiffIterator will yield offset and value for each new byte that is different from original byte. old and new must +// be of the exact same size. I didn't fit old and new value in there cos go only has support for up to Seq2 iter. +func BinDiffIterator(old []byte, new []byte) iter.Seq2[byte, byte] { + return func(yield func(byte, byte) bool) { + var i byte = 0x00 + for _, i = range old { + if new[i] != old[i] { + if !yield(i, new[i]) { + return + } + } + } + } +} diff --git a/internal/pkg/public.go b/internal/pkg/public.go index 0e0984d..d5b1fb8 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -5,6 +5,7 @@ import ( "fmt" "reflect" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick/ssh" ) @@ -286,6 +287,22 @@ func (m *ModuleState) WritePageByteBin(page byte, bank byte, offset byte, value return m.mgmtProtoConcreteStrategy.WritePageByteBin(page, bank, offset, value) } +func (m *ModuleState) WritePageBin(page byte, bank byte, new []byte) error { + old, err := m.GetPageBin(page, bank) + if err != nil { + return err + } + + for offset, newValue := range util.BinDiffIterator(old, new) { + err := m.WritePageByteBin(page, bank, offset, newValue) + if err != nil { + return err + } + } + + return nil +} + func (m *ModuleState) ToJson() ([]byte, error) { marshal, err := json.MarshalIndent(m, "", "\t") if err != nil { From ce20677dab04297e436717248366b9f0e38f5c25 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 17 Jun 2026 17:58:29 +0200 Subject: [PATCH 14/36] flexoptix custom pages (wip) --- internal/pkg/optic/cmis/cmis.go | 51 +++++++++++++++++++++---- internal/pkg/optic/sff8636/flexoptix.go | 10 ++++- internal/pkg/optic/util/bin.go | 27 +++++++++++++ 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index 7feff5f..d5d25bd 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -24,6 +24,8 @@ const ( const ProgOutputPowerPerLaneSupported = 0b1000_0000 +const TunableLaserControlStatusPage = 0x12 + type ManagementStrategy struct { state *pkg.ModuleState } @@ -109,7 +111,7 @@ func (s2 *ManagementStrategy) GetLaserCapabilitiesAdvertising(s *pkg.ModuleState // GetTunableLaserControlStatus maxN between 0 and 7 (channel N, used for only-channel-0 access) func GetTunableLaserControlStatus(s *pkg.ModuleState, bank byte, maxN int) (*pkg.ModuleState, error) { - dumpBin, err := s.GetPageBin(0x12, bank) + dumpBin, err := s.GetPageBin(TunableLaserControlStatusPage, bank) if err != nil { return nil, err } @@ -120,11 +122,11 @@ func GetTunableLaserControlStatus(s *pkg.ModuleState, bank byte, maxN int) (*pkg caps.GridSpacingTxRO[i] = pkg.CMISGridSpacingToFloatGhzMap[caps.GridSpacingTx[i]] caps.FineTuningEnableTx[i] = dumpBin[0x80+i]&pkg.CMISFineTuningEnableTxMask != 0 - caps.ChannelNumberTx[i] = util.ReadBeInt16(dumpBin, 0x88+byte(i)) - caps.FineTuningOffsetTx[i] = util.ReadBeInt16(dumpBin, 0x98+byte(i)) - caps.CurrentLaserFrequencyTx[i] = util.ReadBeUint32(dumpBin, 0xA8+byte(i)) + caps.ChannelNumberTx[i] = util.ReadBeInt16(dumpBin, 0x88+byte(2*i)) // S16 over 2 bytes + caps.FineTuningOffsetTx[i] = util.ReadBeInt16(dumpBin, 0x98+byte(2*i)) + caps.CurrentLaserFrequencyTx[i] = util.ReadBeUint32(dumpBin, 0xA8+byte(2*i)) - caps.TargetOutputPowerTx[i] = util.ReadBeInt16(dumpBin, 0xC8+byte(i)) + caps.TargetOutputPowerTx[i] = util.ReadBeInt16(dumpBin, 0xC8+byte(2*i)) caps.TuningInProgressTx[i] = dumpBin[0xDE+i]&pkg.CMISTuningInProgressTxMask != 0 caps.WaveLengthUnlockStatus[i] = dumpBin[0xDE+i]&pkg.CMISWavelengthUnlockStatusTxMask != 0 @@ -151,8 +153,43 @@ func GetTunableLaserControlStatus(s *pkg.ModuleState, bank byte, maxN int) (*pkg return s, nil } -func GetLaserCapabilitiesAdvertising(s *pkg.ModuleState, bank byte) (*pkg.ModuleState, error) { - dumpBin, err := s.GetPageBin(0x04, bank) +func SetTunableLaserControlStatus(s *pkg.ModuleState, bank byte, maxN int) (*pkg.ModuleState, error) { + dumpBin, err := s.GetPageBin(TunableLaserControlStatusPage, bank) + if err != nil { + return nil, err + } + caps := &s.FlexOptixSFF8636Extension.TunableLaserCtrlStatus + + for i := 0; i <= maxN; i += 1 { + // rewrite to mem + dumpBin[0x80+i] = (caps.GridSpacingTx[i] & pkg.CMISGridSpacingTxMask) & + (util.YesNoByte(caps.FineTuningEnableTx[i]) & pkg.CMISFineTuningEnableTxMask) + + util.WriteBeInt16(caps.ChannelNumberTx[i], dumpBin, 0x88+byte(2*i)) + util.WriteBeInt16(caps.FineTuningOffsetTx[i], dumpBin, 0x98+byte(2*i)) + // no write for CurrentLaserFrequency, read-only + + util.WriteBeInt16(caps.TargetOutputPowerTx[i], dumpBin, 0xC8+byte(2*i)) + // subsequent fields read-only + + dumpBin[0xEF+i] = (util.YesNoByte(caps.TargetOutputPowerOORMaskTx[i]) & pkg.CMISTargetOutputPowerOORFlagTxMask) & + (util.YesNoByte(caps.FineTuningPowerOutOfRangeMaskTx[i]) & pkg.CMISFineTuningOutOfRangeFlagTxMask) & + (util.YesNoByte(caps.TuningNotAcceptedMaskTx[i]) & pkg.CMISTuningNotAcceptedFlagTxMask) & + (util.YesNoByte(caps.InvalidChannelMaskTx[i]) & pkg.CMISInvalidChannelNumberFlagTxMask) & + (util.YesNoByte(caps.WavelengthUnlockedMaskTx[i]) & pkg.CMISWavelengthUnlockedFlagTxMask) & + (util.YesNoByte(caps.TuningCompleteMaskTx[i]) & pkg.CMISTuningCompleteFlagTxMask) + } + + err = s.WritePageBin(TunableLaserControlStatusPage, bank, dumpBin) + if err != nil { + return nil, err + } + + return s, nil +} + +func GetLaserCapabilitiesAdvertising(s *pkg.ModuleState) (*pkg.ModuleState, error) { + dumpBin, err := s.GetPageBin(0x04, 0x00) // non-banked if err != nil { return nil, err } diff --git a/internal/pkg/optic/sff8636/flexoptix.go b/internal/pkg/optic/sff8636/flexoptix.go index c0b26c3..e7adb0f 100644 --- a/internal/pkg/optic/sff8636/flexoptix.go +++ b/internal/pkg/optic/sff8636/flexoptix.go @@ -20,7 +20,7 @@ func NewFlexOptixSFF8636Extension(state *pkg.ModuleState) *FlexOptixSFF8636Manag } func (f *FlexOptixSFF8636ManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { - _, err := cmis.GetLaserCapabilitiesAdvertising(f.state, 0x00) + _, err := cmis.GetLaserCapabilitiesAdvertising(f.state) if err != nil { return nil, err } @@ -34,7 +34,13 @@ func (f *FlexOptixSFF8636ManagementStrategy) GetExtensionState() (*pkg.ModuleSta } func (f *FlexOptixSFF8636ManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { - //TODO implement me + // laser capabilities advertising is all-fields read-only so, no-op for this one + + _, err := cmis.SetTunableLaserControlStatus(f.state, 0x00, 0) + if err != nil { + return nil, err + } + return f.state, nil } diff --git a/internal/pkg/optic/util/bin.go b/internal/pkg/optic/util/bin.go index 9cee083..d12e64f 100644 --- a/internal/pkg/optic/util/bin.go +++ b/internal/pkg/optic/util/bin.go @@ -18,12 +18,28 @@ func TwoComplement16(sizeBits uint8, data uint16) (v int16) { return v } +func TwoComplement16Encode(data int16) (v uint16) { + p := math.Pow(2, 15) // 2^(N-1) + mask := int16(p) + + return uint16(mask - data) +} + func ReadBeUint16(bin []byte, baseOffset byte) uint16 { // CMIS states that the default endianness for numerical data types is big endian // unless stated otherwise return binary.BigEndian.Uint16(bin[baseOffset : baseOffset+0x02]) // go slices use half-open range, like this: [a,b[ } +func WriteBeUint16(i uint16, bin []byte, baseOffset byte) { + var buffer []byte + + binary.BigEndian.PutUint16(buffer, i) + + bin[baseOffset] = buffer[0] + bin[baseOffset] = buffer[1] +} + func ReadBeUint32(bin []byte, baseOffset byte) uint32 { return binary.BigEndian.Uint32(bin[baseOffset : baseOffset+0x04]) // go slices use half-open ranges } @@ -32,6 +48,10 @@ func ReadBeInt16(bin []byte, baseOffset byte) int16 { return TwoComplement16(16, ReadBeUint16(bin, baseOffset)) } +func WriteBeInt16(i int16, bin []byte, baseOffset byte) { + WriteBeUint16(TwoComplement16Encode(i), bin, baseOffset) +} + func ReadBeInt16AndShiftBase(bin []byte, baseOffset *byte) int16 { i := ReadBeInt16(bin, *baseOffset) *baseOffset += 0x02 @@ -58,3 +78,10 @@ func BinDiffIterator(old []byte, new []byte) iter.Seq2[byte, byte] { } } } + +func YesNoByte(value bool) byte { + if value { + return 0xFF + } + return 0x00 +} From 2eb2cc948d76a54a025d2b13016fea5c89a1e045 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 17 Jun 2026 18:43:30 +0200 Subject: [PATCH 15/36] use subcommands for show and make everything an option --- cmd/optic-programmer.go | 128 ++++++++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 39 deletions(-) diff --git a/cmd/optic-programmer.go b/cmd/optic-programmer.go index f2e26aa..b73079a 100644 --- a/cmd/optic-programmer.go +++ b/cmd/optic-programmer.go @@ -43,10 +43,62 @@ var concreteExtensionStrategies = [...]func(state *pkg.ModuleState) pkg.Concrete }, } +var safeModeConcreteExtensionStrategies = [...]func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy{ + // no manufacturers enabled, only lower mem and page 00 + func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy { + return sff8636.NewSFF8636Extension(state) + }, +} + var defaultManagementStrategy = func(state *pkg.ModuleState) pkg.ConcreteManagementStrategy { return _default.New(state) } +var restrictedFeatureSetFactory = func(handle *connection.I2cRWHandle) *pkg.ModuleState { + return pkg.NewModuleState( + defaultManagementStrategy, + concreteManagementStrategies[:], + safeModeConcreteExtensionStrategies[:], + handle, + ) +} + +var allFeatureSetFactory = func(handle *connection.I2cRWHandle) *pkg.ModuleState { + return pkg.NewModuleState( + defaultManagementStrategy, + concreteManagementStrategies[:], + concreteExtensionStrategies[:], + handle, + ) +} + +func ActionTemplateMethod( + moduleFactory func(handle *connection.I2cRWHandle) *pkg.ModuleState, + call func(module *pkg.ModuleState) error) cli.ActionFunc { + return func(_ context.Context, cmd *cli.Command) error { + user := cmd.String("user") + router := cmd.String("device") + iface := cmd.String("interface") + + handle, err := connection.NewI2cRWHandle(user, router, iface) + if err != nil { + return err + } + defer connection.CloseI2CRWHandle(handle) + + module, err := moduleFactory(handle).Get() + if err != nil { + return err + } + err = call(module) + if err != nil { + return err + } + + return nil + } +} + func main() { slog.SetLogLoggerLevel(slog.LevelInfo) if level, ok := os.LookupEnv("LOG_LEVEL"); ok { @@ -59,51 +111,49 @@ func main() { Name: "user", Sources: cli.EnvVars("USER"), }, + &cli.StringFlag{ + Name: "device", + Sources: cli.EnvVars("DEVICE"), + }, + &cli.StringFlag{ + Name: "interface", + Sources: cli.EnvVars("INTERFACE"), + }, }, Commands: []*cli.Command{ { - - Name: "show", - Aliases: []string{"s"}, - Usage: "Shows information about an optic in a specific device", - Arguments: []cli.Argument{ - &cli.StringArg{ - Name: "device", + Name: "show", + Usage: "Shows information about an optic in a specific device", + Commands: []*cli.Command{ + { + Name: "basic", + Action: ActionTemplateMethod(restrictedFeatureSetFactory, func(module *pkg.ModuleState) error { + bytes, err := module.ToJson() + if err != nil { + return err + } + _, err = os.Stdout.Write(bytes) + if err != nil { + return err + } + return nil + }), }, - &cli.StringArg{ - Name: "interface", + { + Name: "all", + Action: ActionTemplateMethod(allFeatureSetFactory, func(module *pkg.ModuleState) error { + bytes, err := module.ToJson() + if err != nil { + return err + } + _, err = os.Stdout.Write(bytes) + if err != nil { + return err + } + return nil + }), }, }, - Action: func(ctx context.Context, command *cli.Command) error { - user := command.String("user") - router := command.StringArg("device") - iface := command.StringArg("interface") - - handle, err := connection.NewI2cRWHandle(user, router, iface) - if err != nil { - return err - } - defer connection.CloseI2CRWHandle(handle) - - module, err := pkg.NewModuleState( - defaultManagementStrategy, - concreteManagementStrategies[:], - concreteExtensionStrategies[:], - handle, - ).Get() - if err != nil { - return err - } - bytes, err := module.ToJson() - if err != nil { - return err - } - _, err = os.Stdout.Write(bytes) - if err != nil { - return err - } - return nil - }, }, { Name: "program", From c17fdc404ecaec2656de6e81da320539e1481a1b Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Thu, 18 Jun 2026 13:28:58 +0200 Subject: [PATCH 16/36] re-implement grid programming for flexoptix modules --- cmd/optic-programmer.go | 118 ++++++++++++++++++++++++-------- internal/pkg/optic/cmis/cmis.go | 59 ++++++++-------- internal/pkg/public.go | 64 +++++++---------- 3 files changed, 149 insertions(+), 92 deletions(-) diff --git a/cmd/optic-programmer.go b/cmd/optic-programmer.go index b73079a..c374df4 100644 --- a/cmd/optic-programmer.go +++ b/cmd/optic-programmer.go @@ -4,14 +4,15 @@ import ( "context" "log/slog" "os" + "strconv" "strings" "github.com/urfave/cli/v3" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/cmis" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/default" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/sff8636" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/routines" connection "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick/ssh" ) @@ -74,8 +75,8 @@ var allFeatureSetFactory = func(handle *connection.I2cRWHandle) *pkg.ModuleState func ActionTemplateMethod( moduleFactory func(handle *connection.I2cRWHandle) *pkg.ModuleState, - call func(module *pkg.ModuleState) error) cli.ActionFunc { - return func(_ context.Context, cmd *cli.Command) error { + call func(module *pkg.ModuleState, context context.Context, cmd *cli.Command) error) cli.ActionFunc { + return func(context context.Context, cmd *cli.Command) error { user := cmd.String("user") router := cmd.String("device") iface := cmd.String("interface") @@ -90,7 +91,7 @@ func ActionTemplateMethod( if err != nil { return err } - err = call(module) + err = call(module, context, cmd) if err != nil { return err } @@ -127,7 +128,11 @@ func main() { Commands: []*cli.Command{ { Name: "basic", - Action: ActionTemplateMethod(restrictedFeatureSetFactory, func(module *pkg.ModuleState) error { + Action: ActionTemplateMethod(restrictedFeatureSetFactory, func( + module *pkg.ModuleState, + context context.Context, + command *cli.Command, + ) error { bytes, err := module.ToJson() if err != nil { return err @@ -141,7 +146,11 @@ func main() { }, { Name: "all", - Action: ActionTemplateMethod(allFeatureSetFactory, func(module *pkg.ModuleState) error { + Action: ActionTemplateMethod(allFeatureSetFactory, func( + module *pkg.ModuleState, + context context.Context, + command *cli.Command, + ) error { bytes, err := module.ToJson() if err != nil { return err @@ -156,30 +165,85 @@ func main() { }, }, { - Name: "program", + Name: "set", Aliases: []string{"s"}, - Usage: "Programs an optic in a specific device", - Arguments: []cli.Argument{ - &cli.StringArg{ - Name: "device", - }, - &cli.StringArg{ - Name: "interface", - }, - }, - Flags: []cli.Flag{ - &cli.IntFlag{ - Name: "grid-spacing", - }, - &cli.IntFlag{ - Name: "channel", - }, - &cli.StringFlag{ - Name: "power", - Value: "low", + Usage: "sets parameter", + Commands: []*cli.Command{ + { + Name: "dwdmgrid", + Description: "For host-programmable DWDM modules, sets channel from grid", + Flags: []cli.Flag{ + &cli.Float64Flag{ + Name: "grid-spacing", + Usage: "grid to use, in ghz", + }, + &cli.IntFlag{ + Name: "channel", + Usage: "channel number to use", + }, + &cli.IntFlag{ + Name: "lane", + Usage: "media lane number (0-numbered)", + }, + }, + Action: ActionTemplateMethod(allFeatureSetFactory, func( + module *pkg.ModuleState, + context context.Context, + command *cli.Command, + ) error { + _, err := module.Get() + if err != nil { + return err + } + + gridSpacing := command.Float64("grid-spacing") + gridSpacingStr := strconv.FormatFloat(gridSpacing, 'f', 3, 64) + channel := command.Int("channel") + lane := command.Int("lane") + + if module.FlexOptixSFF8636Extension.Active { + extension := module.FlexOptixSFF8636Extension + + if !extension.LaserCapabilities.SupportedFrequencies[gridSpacingStr] { + panic("Module does not support this frequency.") + } + + targetFreq := optic.DWDMGridMap[channel] + gridMultiplier := pkg.MultiplierMap[gridSpacingStr] + targetOffset := int16((targetFreq - optic.DWDMCenterFreqHz) / gridMultiplier) + + if targetOffset > extension.LaserCapabilities.GridHighChannel[gridSpacingStr] || + targetOffset < extension.LaserCapabilities.GridLowChannel[gridSpacingStr] { + panic("target offset is above or below maximum frequencies for this grid.") + } + + // flexoptix only supports n = 0 + extension.TunableLaserCtrlStatus.GridSpacingTx[lane] = pkg.FloatGhzToCMISGridSpacing[gridSpacingStr] + extension.TunableLaserCtrlStatus.ChannelNumberTx[lane] = targetOffset + + _, err := module.SetExtensionsState(module) + if err != nil { + return err + } + + bytes, err := module.ToJson() + if err != nil { + return err + } + _, err = os.Stdout.Write(bytes) + if err != nil { + return err + } + } else if module.CMISOnlyExtension.Active { + // pass for now + } else { + panic("Module does not support grid programming") + } + + return nil + }), }, }, - Action: routines.I2CWriteAll, }, }, } diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index d5d25bd..858ab65 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -195,47 +195,52 @@ func GetLaserCapabilitiesAdvertising(s *pkg.ModuleState) (*pkg.ModuleState, erro } caps := &s.FlexOptixSFF8636Extension.LaserCapabilities + caps.SupportedFrequencies = make(map[string]bool) + // I really hate that Go doesn't have bitfields. - caps.GridSupported75Ghz = dumpBin[0x80]&GridSupported75GhzMask != 0 - caps.GridSupported33Ghz = dumpBin[0x80]&GridSupported33GhzMask != 0 - caps.GridSupported100Ghz = dumpBin[0x80]&GridSupported100GhzMask != 0 - caps.GridSupported50Ghz = dumpBin[0x80]&GridSupported50GhzMask != 0 - caps.GridSupported25Ghz = dumpBin[0x80]&GridSupported25GhzMask != 0 - caps.GridSupported12p5Ghz = dumpBin[0x80]&GridSupported12p5GhzMask != 0 - caps.GridSupported6p25Ghz = dumpBin[0x80]&GridSupported6p25GhzMask != 0 - caps.GridSupported3p125Ghz = dumpBin[0x80]&GridSupported3p125GhzMask != 0 + caps.SupportedFrequencies["75.000"] = dumpBin[0x80]&GridSupported75GhzMask != 0 + caps.SupportedFrequencies["33.000"] = dumpBin[0x80]&GridSupported33GhzMask != 0 + caps.SupportedFrequencies["100.000"] = dumpBin[0x80]&GridSupported100GhzMask != 0 + caps.SupportedFrequencies["50.000"] = dumpBin[0x80]&GridSupported50GhzMask != 0 + caps.SupportedFrequencies["25.000"] = dumpBin[0x80]&GridSupported25GhzMask != 0 + caps.SupportedFrequencies["12.500"] = dumpBin[0x80]&GridSupported12p5GhzMask != 0 + caps.SupportedFrequencies["6.250"] = dumpBin[0x80]&GridSupported6p25GhzMask != 0 + caps.SupportedFrequencies["3.125"] = dumpBin[0x80]&GridSupported3p125GhzMask != 0 + caps.SupportedFrequencies["150.000"] = dumpBin[0x81]&GridSupported150GhzMask != 0 caps.FineTuningSupported = dumpBin[0x81]&FineTuningSupportedMask != 0 - caps.GridSupported150Ghz = dumpBin[0x81]&GridSupported150GhzMask != 0 + + caps.GridLowChannel = make(map[string]int16) + caps.GridHighChannel = make(map[string]int16) var base byte = 0x82 // 3.125Ghz - caps.GridLowChannel3p125Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel3p125Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel["3.125"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel["3.125"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 6.25Ghz - caps.GridLowChannel6p25Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel6p25Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel["6.250"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel["6.250"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 12.5Ghz - caps.GridLowChannel12p5Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel12p5Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel["12.500"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel["12.500"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 25Ghz - caps.GridLowChannel25Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel25Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel["25.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel["25.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 50Ghz - caps.GridLowChannel50Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel50Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel["50.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel["50.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 100Ghz - caps.GridLowChannel100Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel100Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel["100.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel["100.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 33Ghz - caps.GridLowChannel33Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel33Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel["33.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel["33.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 75Ghz - caps.GridLowChannel75Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel75Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel["75.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel["75.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 150Ghz - caps.GridLowChannel150Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel150Ghz = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel["150.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel["150.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) base = 0xBE // skip reserved region caps.FineTuningResolution = util.ReadBeUint16AndShiftBase(dumpBin, &base) diff --git a/internal/pkg/public.go b/internal/pkg/public.go index d5b1fb8..364cbc4 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -52,40 +52,16 @@ type CMISSupportedControlsAdvertising struct { type CMISLaserCapabilitiesAdvertising struct { // page 04h laser capabilities adv // is channel-based grid tuning supported on these frequencies - GridSupported3p125Ghz bool - GridSupported6p25Ghz bool - GridSupported12p5Ghz bool - GridSupported25Ghz bool - GridSupported33Ghz bool - GridSupported50Ghz bool - GridSupported75Ghz bool - GridSupported100Ghz bool - GridSupported150Ghz bool + SupportedFrequencies map[string]bool // is fine-tuning supported in the vicinity of an on-grid channel FineTuningSupported bool // S16 encoded lowest N for spacing for each freq - GridLowChannel3p125Ghz int16 - GridLowChannel6p25Ghz int16 - GridLowChannel12p5Ghz int16 - GridLowChannel25Ghz int16 - GridLowChannel33Ghz int16 - GridLowChannel50Ghz int16 - GridLowChannel100Ghz int16 - GridLowChannel75Ghz int16 - GridLowChannel150Ghz int16 + GridLowChannel map[string]int16 // S16 encoded higher N for spacing for each freq - GridHighChannel3p125Ghz int16 - GridHighChannel6p25Ghz int16 - GridHighChannel12p5Ghz int16 - GridHighChannel25Ghz int16 - GridHighChannel33Ghz int16 - GridHighChannel50Ghz int16 - GridHighChannel100Ghz int16 - GridHighChannel75Ghz int16 - GridHighChannel150Ghz int16 + GridHighChannel map[string]int16 // fine-tuning res, 0.001 Ghz increments FineTuningResolution uint16 @@ -123,16 +99,28 @@ var CMISGridSpacingToFloatGhzMap = map[byte]float64{ CMISGridSpacing150Ghz: 150.0, } -var FloatGhzToCMISGridSpacing = map[float64]int8{ - 3.125: CMISGridSpacing3p125Ghz, - 6.25: CMISGridSpacing6p25Ghz, - 12.5: CMISGridSpacing12p5Ghz, - 25.0: CMISGridSpacing25Ghz, - 50.0: CMISGridSpacing50Ghz, - 100.0: CMISGridSpacing100Ghz, - 33.0: CMISGridSpacing33Ghz, - 75.0: CMISGridSpacing75Ghz, - 150.0: CMISGridSpacing150Ghz, +var FloatGhzToCMISGridSpacing = map[string]byte{ // cannot use float as key since not serializable + "3.125": CMISGridSpacing3p125Ghz, + "6.250": CMISGridSpacing6p25Ghz, + "12.500": CMISGridSpacing12p5Ghz, + "25.000": CMISGridSpacing25Ghz, + "50.000": CMISGridSpacing50Ghz, + "100.000": CMISGridSpacing100Ghz, + "33.000": CMISGridSpacing33Ghz, + "75.000": CMISGridSpacing75Ghz, + "150.000": CMISGridSpacing150Ghz, +} + +var MultiplierMap = map[string]int{ + "3.125": 3.125e9, + "6.250": 6.25e9, + "12.500": 12.5e9, + "25.000": 25.0e9, + "50.000": 50.0e9, + "100.000": 100.0e9, + "33.000": 33.0e9, + "75.000": 75.0e9, + "150.000": 150.0e9, } const ( @@ -347,7 +335,7 @@ func (m *ModuleState) SetExtensionsState(s *ModuleState) (*ModuleState, error) { } func (m *ModuleState) GetExtensionsState() (*ModuleState, error) { - for _, e := range m.mgmtProtoExtensionsConcreteStrategies { + for _, e := range m.mgmtProtoExtensionsConcreteStrategies { // multiple extensions may be active _, err := e.GetExtensionState() // force refresh if err != nil { return nil, err From b565ec6a9b190c90200369f43c3fe8e54f913e5c Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Mon, 22 Jun 2026 18:42:36 +0200 Subject: [PATCH 17/36] cmis implementation wip --- cmd/optic-programmer.go | 6 + internal/pkg/optic/cmis/cmis.go | 395 +++++++++++++++++++++--- internal/pkg/optic/sff8636/flexoptix.go | 10 +- internal/pkg/optic/sff8636/sff8636.go | 9 +- internal/pkg/public.go | 92 +++++- 5 files changed, 452 insertions(+), 60 deletions(-) diff --git a/cmd/optic-programmer.go b/cmd/optic-programmer.go index c374df4..20bec1c 100644 --- a/cmd/optic-programmer.go +++ b/cmd/optic-programmer.go @@ -42,6 +42,9 @@ var concreteExtensionStrategies = [...]func(state *pkg.ModuleState) pkg.Concrete func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy { return sff8636.NewFlexOptixSFF8636Extension(state) }, + func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy { + return cmis.NewCMISExtension(state) + }, } var safeModeConcreteExtensionStrategies = [...]func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy{ @@ -49,6 +52,9 @@ var safeModeConcreteExtensionStrategies = [...]func(state *pkg.ModuleState) pkg. func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy { return sff8636.NewSFF8636Extension(state) }, + func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy { + return cmis.NewCMISExtension(state) + }, } var defaultManagementStrategy = func(state *pkg.ModuleState) pkg.ConcreteManagementStrategy { diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index 858ab65..d26c8a4 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -1,12 +1,18 @@ package cmis import ( + "fmt" "slices" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" ) +const ( + BankSelectRegisterAddress = 0x7E + PageSelectRegisterAddress = 0x7F +) + const ( GridSupported75GhzMask = 0b1000_0000 >> iota GridSupported33GhzMask @@ -22,28 +28,310 @@ const ( GridSupported150GhzMask ) -const ProgOutputPowerPerLaneSupported = 0b1000_0000 +const ( + ProgOutputPowerPerLaneSupported = 0b1000_0000 + MemoryModelMask = 0b1000_0000 + SteppedConfigOnlyMask = 0b0100_0000 + I2CMciMaxSpeedMask = 0b0011_0000 + SPIMCIMaxSpeedMask = 0b0000_1100 + AutoCommissioningMask = 0b0000_0011 + ModulePowerClassMask = 0b1110_0000 + FarEndConfigurationMask = 0b0001_0000 + BanksSupportedMask = 0b0000_0011 + ExtraLaneBanksSupportedMask = 0b0001_1111 +) + +const ( + WavelengthIsControllableMask = 0b1000_0000 + TransmitterIsTunableMask = 0b0100_0000 + SquelchMethodTxMask = 0b0011_0000 + ForcedSquelchTxSupportedMask = 0b0000_1000 + AutoSquelchDisableTxSupportedMask = 0b0000_0100 + OutputDisableTxSupportedMask = 0b0000_0010 + InputPolarityFlipTxSupportedMask = 0b0000_0001 + BankBroadcastSupportedMask = 0b1000_0000 + AutoSquelchDisableRxSupportedMask = 0b0000_0100 + OutputDisableRxSupportedMask = 0b0000_0010 + OutputPolarityFlipRxSupportedMask = 0b0000_0001 +) const TunableLaserControlStatusPage = 0x12 +const MaximumLaneNumber = 7 + +var I2CMciMaxSpeedToKhz = map[byte]int{ + 0x00: 400, + 0x10: 1_000, + 0x20: 3_400, + 0x30: 0, // reserved onwards + 0x40: 0, + 0x50: 0, + 0x60: 0, + 0x70: 0, + 0x80: 0, + 0x90: 0, + 0xA0: 0, + 0xB0: 0, + 0xC0: 0, + 0xD0: 0, + 0xE0: 0, + 0xF0: 0, +} + +var SPIMciMaxSpeedToKhz = map[byte]int{ + 0x00: 1_000, + 0x01: 2_000, + 0x02: 4_000, + 0x03: 8_000, + 0x04: 12_000, + 0x05: 16_000, + 0x06: 20_000, + 0x07: 30_000, + 0x08: 40_000, + 0x09: 50_000, + 0x0A: 0, // reserved onwards + 0x0B: 0, + 0x0C: 0, + 0x0D: 0, + 0x0E: 0, + 0x0F: 0, +} + +var PowerClassToInt = map[byte]int{ + 0b0000_0000: 1, + 0b0010_0000: 2, + 0b0100_0000: 3, + 0b0110_0000: 4, + 0b1000_0000: 5, + 0b1010_0000: 6, + 0b1100_0000: 7, + 0b1110_0000: 8, +} + +var MediaInterfaceToStr = map[byte]string{ + 0x00: "850nm VCSEL", + 0x01: "1310nm VCSEL", + 0x02: "1550nm VCSEL", + 0x03: "1310nm FP", + 0x04: "1310nm DFB", + 0x05: "1550nm DFB", + 0x06: "1310nm EML", + 0x07: "1550nm EML", + 0x08: "Other", + 0x09: "1490nm DFB", + 0x0A: "Copper cable, passive, unequalized", + 0x0B: "Copper cable, passive, equalized", + 0x0C: "Copper cable with near and far end limiting active equalizers", + 0x0D: "Copper cable with end limiting active equalizers", + 0x0E: "Copper cable with near end limiting active equalizers", + 0x0F: "Copper cable with linear active equalizers", + 0x10: "C-band tunable laser", + 0x11: "L-band tunable laser", + 0x12: "Copper cable with near and far end linear active equalizers", + 0x13: "Copper cable with far end linear active equalizers", + 0x14: "Copper cable with near end linear active equalizers", +} + +const BankSelectErrorString = "I could not write to bank select register, aborting program now." +const PageSelectErrorString = "I could not write to page select register, aborting program now." +const RegisterWriteErrorString = "I could not write to arbitrary register, aborting program now." +const PageOrBankNotAvailableTemplateErrorString = "Module responded that page 0x%x with bank 0x%x is not available," + + " its likely I failed to correctly identify the module capabilities. Aborting program now." type ManagementStrategy struct { state *pkg.ModuleState } +type ExtensionManagementStrategy struct { + state *pkg.ModuleState +} + func New(state *pkg.ModuleState) *ManagementStrategy { return &ManagementStrategy{ state: state, } } -func (s2 *ManagementStrategy) GetPageBin(page byte, bank byte) ([]byte, error) { +func NewCMISExtension(state *pkg.ModuleState) *ExtensionManagementStrategy { + return &ExtensionManagementStrategy{ + state: state, + } +} + +func (e *ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { + _, err := e.GetSupportedControlsAdvertising() + if err != nil { + return nil, err + } + _, err = e.GetLaserCapabilitiesAdvertising() + if err != nil { + return nil, err + } + _, err = e.GetTunableLaserControlStatus() + if err != nil { + return nil, err + } + + return e.state, nil +} + +func (e *ExtensionManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } +func (e *ExtensionManagementStrategy) ManufacturerIsCompatibleWithProtocolExtension(manufacturer string) bool { + return true // manufacturer names have 0 influence, only sff8024 does. so we defer decision +} + +func (e *ExtensionManagementStrategy) SFF8024IsCompatibleWithProtocolExtension( + sff8024Identifier byte, + sff8024Revision byte, +) bool { + return checkSFF8024(sff8024Identifier, sff8024Revision) +} + +func (e *ExtensionManagementStrategy) Activate() (*pkg.ModuleState, error) { + e.state.CMISOnlyExtension.Active = true + return e.state, nil +} + +func (e *ExtensionManagementStrategy) GetTunableLaserControlStatus() (*pkg.ModuleState, error) { + // _, err := e.state.GetAdministrativeInformation() should already have been fetched + if !e.state.CMISOnlyExtension.MemoryModelPaged { + return e.state, nil // noop + } + + var bank byte + for bank = 0x00; bank <= e.state.CMISOnlyExtension.SupportedControls.MaximumBankSupported; bank += 1 { + _, err := GetTunableLaserControlStatus( + e.state, &e.state.CMISOnlyExtension.TunableLaserCtrlStatus[bank], bank, MaximumLaneNumber, + ) + if err != nil { + return nil, err + } + } + + return e.state, nil +} + +func (e *ExtensionManagementStrategy) GetLaserCapabilitiesAdvertising() (*pkg.ModuleState, error) { + if !e.state.CMISOnlyExtension.MemoryModelPaged { + return e.state, nil // noop + } + + _, err := GetLaserCapabilitiesAdvertising( + e.state, &e.state.CMISOnlyExtension.LaserCapabilities, + ) + if err != nil { + return nil, err + } + + return e.state, nil +} + +func (e *ExtensionManagementStrategy) GetSupportedControlsAdvertising() (*pkg.ModuleState, error) { + // _, err2 := e.state.GetAdministrativeInformation() should already have been fetched + // check if page 01 is supported + if !e.state.CMISOnlyExtension.MemoryModelPaged { + return e.state, nil // noop + } + + dumpBin, err := e.state.GetPageBin(0x01, 0x00) + if err != nil { + return nil, err + } + + caps := &e.state.CMISOnlyExtension.SupportedControls + + caps.ModuleInactiveFirmwareMajorRevision = dumpBin[0x80] + caps.ModuleInactiveFirmwareMinorRevision = dumpBin[0x81] + caps.ModuleHardwareMajorRevision = dumpBin[0x82] + caps.ModuleHardwareMinorRevision = dumpBin[0x83] + + // skipping supported lengths + + // only defined for SINGLE WAVELENGTH modules + var base byte = 0x8A + caps.NominalWavelengthNm = 0.05 * float64(util.ReadBeUint16AndShiftBase(dumpBin, &base)) + caps.WavelengthToleranceNm = 0.005 * float64(util.ReadBeUint16AndShiftBase(dumpBin, &base)) + + // skipping network / vdm / diag / coherent / cmisff / 03h + caps.MaximumBankSupported = dumpBin[0x8E] & BanksSupportedMask + if caps.MaximumBankSupported == 0x03 { // signals lane banked + caps.MaximumBankSupported = dumpBin[0xAE] & ExtraLaneBanksSupportedMask + } + + // go should have bitfields + caps.WavelengthIsControllable = dumpBin[0x9B]&WavelengthIsControllableMask != 0 + caps.TransmitterIsTunable = dumpBin[0x9B]&TransmitterIsTunableMask != 0 + caps.SquelchMethodTx = dumpBin[0x9B] & SquelchMethodTxMask + caps.ForcedSquelchTxSupported = dumpBin[0x9B]&ForcedSquelchTxSupportedMask != 0 + caps.AutoSquelchDisableTxSupported = dumpBin[0x9B]&AutoSquelchDisableTxSupportedMask != 0 + caps.OutputDisableTxSupported = dumpBin[0x9B]&OutputDisableTxSupportedMask != 0 + caps.InputPolarityFlipTxSupported = dumpBin[0x9B]&InputPolarityFlipTxSupportedMask != 0 + caps.BankBroadcastSupported = dumpBin[0x9C]&BankBroadcastSupportedMask != 0 + caps.AutoSquelchDisableRxSupported = dumpBin[0x9C]&AutoSquelchDisableRxSupportedMask != 0 + caps.OutputDisableRxSupported = dumpBin[0x9C]&OutputDisableRxSupportedMask != 0 + caps.OutputPolarityFlipRxSupported = dumpBin[0x9C]&OutputPolarityFlipRxSupportedMask != 0 + + return e.state, nil +} + +func (s2 *ManagementStrategy) GetPageBin(page byte, bank byte) ([]byte, error) { + handle := s2.state.GetHandle() + + // we do not have one-shot 2 bytes write available + // currently this is out of spec + // > Note: In other words, a host needs to write both the BankSelect and the PageSelect register in a single WRITE + // > transaction, except for the case when BankIndex value in the BankSelect register does not change. In this case + // > it is sufficient to WRITE to the PageSelect register. + // > Note: However, modules may also choose to accept the two registers written in two subsequent WRITE + // > transactions, to work with non-compliant hosts. Note that rainy day scenarios remain unspecified in this case, + // > and the PageMapping Validity assertion described below is compromised. + + // do bank select first (will not be cleared if incorrect) + err := handle.Connection.DoI2CSet(handle.I2cBusId, BankSelectRegisterAddress, bank) + if err != nil { + panic(BankSelectErrorString) + } + + // do page select next (will be cleared if page + bank is incorrect) + err = handle.Connection.DoI2CSet(handle.I2cBusId, PageSelectRegisterAddress, page) + if err != nil { + panic(PageSelectErrorString) + } + + // decode page + pageStr, err := handle.Connection.GetI2CDump(handle.I2cBusId) + if err != nil { + return []byte{}, err + } + dumpBin := pkg.ParseI2CDump(*pageStr) + + // check page + bank select was authorized only and only if target page select = page select + if dumpBin[PageSelectRegisterAddress] != page { + panic(fmt.Sprintf(PageOrBankNotAvailableTemplateErrorString, page, bank)) + } + + // return if everything went ok + return dumpBin, nil +} + func (s2 *ManagementStrategy) WritePageByteBin(page byte, bank byte, offset byte, value byte) error { - //TODO implement me - panic("implement me") + handle := s2.state.GetHandle() + + _, err := s2.GetPageBin(page, bank) // select page by read + if err != nil { + return err + } + + // write is authorized if nothing failed + err = handle.Connection.DoI2CSet(handle.I2cBusId, (int)(offset), value) + if err != nil { + panic(RegisterWriteErrorString) + } + + return nil } const RefusalMajorTooHigh string = "This CMIS module has" + @@ -52,7 +340,7 @@ const RefusalMajorTooHigh string = "This CMIS module has" + "now, as I cannot read nor write to this module without " + "potential failure, data loss and/or equipment damage." -func (s2 *ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { +func checkSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { var CmisCompatibleSFF8024IDs = [...]byte{ 0x1E, // qsfp+ or later with cmis 0x1F, // sfp-dd with cmis @@ -62,13 +350,14 @@ func (s2 *ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revi 0x23, // 4 lanes cdfp with cmis 0x24, // 8 lanes cdfp with cmis 0x25, // 16 lanes cdfp with cmis + 0x18, // qsfp-dd 8x - may support cmis } - const MajVerMask byte = 0b111_1000 // upper nibble is bits 7-4 per cmis rev 5.38 section 8.2.1 - const MinMajVer byte = 0x05 // minimum supported major version is 5 + const MajVerMask byte = 0xF0 // upper nibble is bits 7-4 per cmis rev 5.38 section 8.2.1 + const MinMajVer byte = 0x50 // minimum supported major version is 5 compatibleIdentifier := func(id byte) bool { return slices.Contains(CmisCompatibleSFF8024IDs[:], id) } - compatibleMajorRev := func(sff8024rev byte) bool { return (sff8024Identifier & MajVerMask) <= MinMajVer } + compatibleMajorRev := func(sff8024rev byte) bool { return (sff8024rev & MajVerMask) <= MinMajVer } // panic if CMIS and revision is above our maximum if compatibleIdentifier(sff8024Identifier) && !compatibleMajorRev(sff8024Revision) { @@ -79,19 +368,68 @@ func (s2 *ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revi return compatibleIdentifier(sff8024Identifier) } +func (s2 *ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { + return checkSFF8024(sff8024Identifier, sff8024Revision) +} + func (s2 *ManagementStrategy) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { //TODO implement me panic("implement me") } func (s2 *ManagementStrategy) Get() (*pkg.ModuleState, error) { - //TODO implement me - panic("implement me") + _, err := s2.GetAdministrativeInformation() + if err != nil { + return nil, err + } + return s2.state, nil } func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { - //TODO implement me - panic("implement me") + dumpBin, err := s2.state.GetPageBin(0x00, 0x00) + if err != nil { + return nil, err + } + + s2.state.ManagementProtocol = "cmis" + + // lower mem + s2.state.CMISOnlyExtension.MemoryModelPaged = dumpBin[0x02]&MemoryModelMask == 0 // 0 is paged memory, 1 is flat + s2.state.CMISOnlyExtension.SteppedConfigOnly = dumpBin[0x02]&SteppedConfigOnlyMask == 0 + s2.state.CMISOnlyExtension.I2CMciMaxSpeedKhz = I2CMciMaxSpeedToKhz[dumpBin[0x02]&I2CMciMaxSpeedMask] + s2.state.CMISOnlyExtension.SPIMciMaxSpeedKhz = SPIMciMaxSpeedToKhz[dumpBin[0x02]&SPIMCIMaxSpeedMask] + AutoCommissioning := dumpBin[0x02] & AutoCommissioningMask + s2.state.CMISOnlyExtension.AutoCommissioningNone = AutoCommissioning == 0b0000 + s2.state.CMISOnlyExtension.AutoCommissioningRegular = AutoCommissioning == 0b0001 + s2.state.CMISOnlyExtension.AutoCommissioningHot = AutoCommissioning == 0b0010 // 0x11 is reserved + + // page 00 + s2.state.VendorName = util.ParseASCIIToString(dumpBin[0x81:0x90]) + s2.state.CMISOnlyExtension.VendorOUI = dumpBin[0x91:0x93] + s2.state.VendorPartNumber = util.ParseASCIIToString(dumpBin[0x94:0xA3]) + s2.state.VendorPartRevision = util.ParseASCIIToString(dumpBin[0xA4:0xA5]) + s2.state.VendorSerialNumber = util.ParseASCIIToString(dumpBin[0xA6:0xB5]) + s2.state.CMISOnlyExtension.DateCode = util.ParseASCIIToString(dumpBin[0xB6:0xBD]) + s2.state.CMISOnlyExtension.CLEICode = util.ParseASCIIToString(dumpBin[0xBE:0xC7]) + s2.state.CMISOnlyExtension.PowerClass = PowerClassToInt[dumpBin[0xC8]&ModulePowerClassMask] + s2.state.CMISOnlyExtension.MaxPowerWatts = 0.25 * float64(dumpBin[0xC9]) // byte is interpreted as uint8. unit is ceil of quarter-watts + + s2.state.CMISOnlyExtension.SupportedMediaLanes = make(map[int]bool) + // fetching media lane support per lane + var mask byte = 0b1000_0000 + for i := 8; i >= 1; i -= 1 { // MSB countdown + s2.state.CMISOnlyExtension.SupportedMediaLanes[i] = dumpBin[0xD2]&mask == 0 // supported == 0, unsupported == 1 + mask >>= 1 + } + s2.state.CMISOnlyExtension.FarEndDetachableMedia = dumpBin[0xD3]&FarEndConfigurationMask == 0b0000_0000 + s2.state.CMISOnlyExtension.FarEnd1LaneModule = dumpBin[0xD3]&FarEndConfigurationMask == 0b0000_0001 + s2.state.CMISOnlyExtension.FarEnd2LanesModule = dumpBin[0xD3]&FarEndConfigurationMask == 0b000_0110 + s2.state.CMISOnlyExtension.FarEnd4LanesModule = dumpBin[0xD3]&FarEndConfigurationMask == 0b000_0011 + s2.state.CMISOnlyExtension.FarEnd8LanesModule = dumpBin[0xD3]&FarEndConfigurationMask == 0b000_0010 + s2.state.CMISOnlyExtension.FarEnd16LanesModule = dumpBin[0xD3]&FarEndConfigurationMask == 0b001_1011 + s2.state.CMISOnlyExtension.MediaInterface = MediaInterfaceToStr[dumpBin[0xD4]] + + return s2.state, nil } func (s2 *ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { @@ -99,23 +437,12 @@ func (s2 *ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) ( panic("implement me") } -func (s2 *ManagementStrategy) GetTunableLaserControlStatus(s *pkg.ModuleState) { - // TODO implement me - panic("implement me") -} - -func (s2 *ManagementStrategy) GetLaserCapabilitiesAdvertising(s *pkg.ModuleState) (*pkg.ModuleState, error) { - // TODO implement me - panic("implement me") -} - // GetTunableLaserControlStatus maxN between 0 and 7 (channel N, used for only-channel-0 access) -func GetTunableLaserControlStatus(s *pkg.ModuleState, bank byte, maxN int) (*pkg.ModuleState, error) { - dumpBin, err := s.GetPageBin(TunableLaserControlStatusPage, bank) +func GetTunableLaserControlStatus(state *pkg.ModuleState, caps *pkg.CMISBankedTunableLaserControlAndStatus, bank byte, maxN int) (*pkg.ModuleState, error) { + dumpBin, err := state.GetPageBin(TunableLaserControlStatusPage, bank) if err != nil { return nil, err } - caps := &s.FlexOptixSFF8636Extension.TunableLaserCtrlStatus for i := 0; i <= maxN; i += 1 { caps.GridSpacingTx[i] = dumpBin[0x80+i] & pkg.CMISGridSpacingTxMask @@ -150,15 +477,14 @@ func GetTunableLaserControlStatus(s *pkg.ModuleState, bank byte, maxN int) (*pkg caps.TuningCompleteMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISTuningCompleteFlagTxMask != 0 } - return s, nil + return state, nil } -func SetTunableLaserControlStatus(s *pkg.ModuleState, bank byte, maxN int) (*pkg.ModuleState, error) { - dumpBin, err := s.GetPageBin(TunableLaserControlStatusPage, bank) +func SetTunableLaserControlStatus(state *pkg.ModuleState, caps *pkg.CMISBankedTunableLaserControlAndStatus, bank byte, maxN int) (*pkg.ModuleState, error) { + dumpBin, err := state.GetPageBin(TunableLaserControlStatusPage, bank) if err != nil { return nil, err } - caps := &s.FlexOptixSFF8636Extension.TunableLaserCtrlStatus for i := 0; i <= maxN; i += 1 { // rewrite to mem @@ -180,20 +506,19 @@ func SetTunableLaserControlStatus(s *pkg.ModuleState, bank byte, maxN int) (*pkg (util.YesNoByte(caps.TuningCompleteMaskTx[i]) & pkg.CMISTuningCompleteFlagTxMask) } - err = s.WritePageBin(TunableLaserControlStatusPage, bank, dumpBin) + err = state.WritePageBin(TunableLaserControlStatusPage, bank, dumpBin) if err != nil { return nil, err } - return s, nil + return state, nil } -func GetLaserCapabilitiesAdvertising(s *pkg.ModuleState) (*pkg.ModuleState, error) { - dumpBin, err := s.GetPageBin(0x04, 0x00) // non-banked +func GetLaserCapabilitiesAdvertising(state *pkg.ModuleState, caps *pkg.CMISLaserCapabilitiesAdvertising) (*pkg.ModuleState, error) { + dumpBin, err := state.GetPageBin(0x04, 0x00) // non-banked if err != nil { return nil, err } - caps := &s.FlexOptixSFF8636Extension.LaserCapabilities caps.SupportedFrequencies = make(map[string]bool) @@ -253,5 +578,5 @@ func GetLaserCapabilitiesAdvertising(s *pkg.ModuleState) (*pkg.ModuleState, erro caps.ProgOutputPowerMax = util.ReadBeInt16AndShiftBase(dumpBin, &base) // TODO checksum support - return s, nil + return state, nil } diff --git a/internal/pkg/optic/sff8636/flexoptix.go b/internal/pkg/optic/sff8636/flexoptix.go index e7adb0f..80db753 100644 --- a/internal/pkg/optic/sff8636/flexoptix.go +++ b/internal/pkg/optic/sff8636/flexoptix.go @@ -20,12 +20,14 @@ func NewFlexOptixSFF8636Extension(state *pkg.ModuleState) *FlexOptixSFF8636Manag } func (f *FlexOptixSFF8636ManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { - _, err := cmis.GetLaserCapabilitiesAdvertising(f.state) + _, err := cmis.GetLaserCapabilitiesAdvertising(f.state, &f.state.FlexOptixSFF8636Extension.LaserCapabilities) if err != nil { return nil, err } - _, err = cmis.GetTunableLaserControlStatus(f.state, 0x00, 0) + _, err = cmis.GetTunableLaserControlStatus( + f.state, &f.state.FlexOptixSFF8636Extension.TunableLaserCtrlStatus, 0x00, 0, + ) if err != nil { return nil, err } @@ -36,7 +38,9 @@ func (f *FlexOptixSFF8636ManagementStrategy) GetExtensionState() (*pkg.ModuleSta func (f *FlexOptixSFF8636ManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { // laser capabilities advertising is all-fields read-only so, no-op for this one - _, err := cmis.SetTunableLaserControlStatus(f.state, 0x00, 0) + _, err := cmis.SetTunableLaserControlStatus( + f.state, &f.state.FlexOptixSFF8636Extension.TunableLaserCtrlStatus, 0x00, 0, + ) if err != nil { return nil, err } diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index b245518..692e35c 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -153,7 +153,6 @@ func (s2 *ManagementStrategy) Get() (*pkg.ModuleState, error) { if err != nil { return nil, err } - // TODO call the rest of getters return s2.state, nil } @@ -175,10 +174,10 @@ func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, s2.state.SFF8024Identifier = bin[0x00] s2.state.SFF8024Revision = bin[0x01] s2.state.SoftwareReset = bin[0x5D]&SoftwareResetMask == SoftwareResetMask - s2.state.EnableHighPowerClass8 = bin[0x5D]&EnableHighPowerClass8Mask == EnableHighPowerClass8Mask - s2.state.EnableHighPowerClass57 = bin[0x5D]&EnableHighPowerClass57Mask == EnableHighPowerClass57Mask - s2.state.LowPwrRequestSW = bin[0x5D]&LowPwrRequestSWMask == LowPwrRequestSWMask - s2.state.LowPwrOverride = bin[0x5D]&LowPwrOverrideMask == LowPwrOverrideMask + s2.state.SFF8636OnlyExtension.EnableHighPowerClass8 = bin[0x5D]&EnableHighPowerClass8Mask == EnableHighPowerClass8Mask + s2.state.SFF8636OnlyExtension.EnableHighPowerClass57 = bin[0x5D]&EnableHighPowerClass57Mask == EnableHighPowerClass57Mask + s2.state.SFF8636OnlyExtension.LowPwrRequestSW = bin[0x5D]&LowPwrRequestSWMask == LowPwrRequestSWMask + s2.state.SFF8636OnlyExtension.LowPwrOverride = bin[0x5D]&LowPwrOverrideMask == LowPwrOverrideMask // page 0x00 s2.state.VendorName = util.ParseASCIIToString(bin[0x94:0xA3]) diff --git a/internal/pkg/public.go b/internal/pkg/public.go index 364cbc4..a934e59 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -29,14 +29,69 @@ type FlexOptixSFF8636Extension struct { type CMISOnlyExtension struct { Active bool - // TODO implement - // SupportedControls CMISSupportedControlsAdvertising - // LaserCapabilities CMISLaserCapabilitiesAdvertising - // TunableLaserCtrlStatus [4]CMISBankedTunableLaserControlAndStatus // CMIS 5.3 defines 4 banks max. + // lower mem + MemoryModelPaged bool // MemoryModelPaged is true if paged, false if flat (flat is lower + page 0x00 only) + SteppedConfigOnly bool // SteppedConfigOnly true if all types of reconfiguration (step by step hot + regular), false if step by step / none autocomm. + I2CMciMaxSpeedKhz int // I2CMciMaxSpeedKhz Maximum I2C MCI interface speed in Khz + SPIMciMaxSpeedKhz int // SPIMciMaxSpeedKhz Maximum MCI SPI interface speed in Khz + AutoCommissioningNone bool // AutoCommissioningNone true if no auto-commissioning is supported + AutoCommissioningRegular bool // AutoCommissioningRegular true if regular auto-commissioning is supported (Affects ApplyDPInit) + AutoCommissioningHot bool // AutoCommissioningHot true if only hot auto-commissioning is supported (Affects ApplyImmediate) + + // page 00, all + VendorOUI []byte + DateCode string + CLEICode string + PowerClass int + MaxPowerWatts float64 // MaxPowerWatts is in multiples of 0.25W, ceil. + + // page 00, copper and active + // CableAssemblyLengthMeters uint + // ConnectorType byte + // AttenuationAt5Ghz uint8 + // AttenuationAt7Ghz uint8 + // AttenuationAt12p9Ghz uint8 + // AttenuationAt25p8Ghz uint8 + // AttenuationAt53p1Ghz uint8 + + // page 00, cont. + SupportedMediaLanes map[int]bool + FarEndDetachableMedia bool + FarEnd1LaneModule bool + FarEnd2LanesModule bool + FarEnd4LanesModule bool + FarEnd8LanesModule bool + FarEnd16LanesModule bool + MediaInterface string + + // page 01, optional + SupportedControls CMISSupportedControlsAdvertising `json:",omitzero"` + + // supported flags tbd. + // supported monitors tbd. + // supported configuration and signal integrity controls adv tbd. + // CDB messaging support advertisement tbd. + // additional durations adv tbd. + // host lane polarity inversion adv tbd. + // supported pages and banks adv tbd. + // NAD banks + media lane assignment + additional app adv tbd. + // misc feature adv tbd. + + // page 12 + LaserCapabilities CMISLaserCapabilitiesAdvertising `json:",omitzero"` + TunableLaserCtrlStatus [4]CMISBankedTunableLaserControlAndStatus `json:",omitzero"` // CMIS 5.3 defines 4 banks max. (0-3 32 lanes) } type CMISSupportedControlsAdvertising struct { - // page 01h supported controls adv + ModuleInactiveFirmwareMajorRevision uint8 + ModuleInactiveFirmwareMinorRevision uint8 + ModuleHardwareMajorRevision uint8 + ModuleHardwareMinorRevision uint8 + // link length support tbd. + NominalWavelengthNm float64 // NominalWavelengthNm at room temp. + WavelengthToleranceNm float64 // WavelengthToleranceNm worst case tolerance around nominal + MaximumBankSupported byte // MaximumBankSupported upper bank limit (0, 0-1, 0-3 for 8, 16, 32 lanes respectively) + // module characteristics adv. tbd. WavelengthIsControllable bool TransmitterIsTunable bool SquelchMethodTx byte @@ -49,6 +104,7 @@ type CMISSupportedControlsAdvertising struct { OutputPolarityFlipRxSupported bool BankBroadcastSupported bool } + type CMISLaserCapabilitiesAdvertising struct { // page 04h laser capabilities adv // is channel-based grid tuning supported on these frequencies @@ -178,6 +234,12 @@ type CMISBankedTunableLaserControlAndStatus struct { // SFF8636OnlyExtension SFF8636 specific information type SFF8636OnlyExtension struct { Active bool + + // lower mem + EnableHighPowerClass8 bool + EnableHighPowerClass57 bool + LowPwrRequestSW bool + LowPwrOverride bool } // ModuleState is data exchange interface - delegates to strategy @@ -189,20 +251,16 @@ type ModuleState struct { mgmtProtoExtensionsConcreteStrategies []ConcreteExtensionManagementStrategy handle *connection.I2cRWHandle // private pointer to connection handle. - FinIsarCMISExtension FinIsarCMISExtension - FlexOptixSFF8636Extension FlexOptixSFF8636Extension - SFF8636OnlyExtension SFF8636OnlyExtension - CMISOnlyExtension CMISOnlyExtension + FinIsarCMISExtension FinIsarCMISExtension `json:",omitzero"` + FlexOptixSFF8636Extension FlexOptixSFF8636Extension `json:",omitzero"` + SFF8636OnlyExtension SFF8636OnlyExtension `json:",omitzero"` + CMISOnlyExtension CMISOnlyExtension `json:",omitzero"` // lower mem region - ManagementProtocol string - SFF8024Identifier uint8 // SFF8024Identifier lower mem public read-only sff8024 id field - SFF8024Revision uint8 // SFF8024Revision lower mem public read-only sff8024 revision id field - SoftwareReset bool - EnableHighPowerClass8 bool // TODO check if PowerClasses are present in CMIS too - EnableHighPowerClass57 bool - LowPwrRequestSW bool - LowPwrOverride bool + ManagementProtocol string + SFF8024Identifier uint8 // SFF8024Identifier lower mem public read-only sff8024 id field + SFF8024Revision uint8 // SFF8024Revision lower mem public read-only sff8024 revision id field + SoftwareReset bool // page 00 region, common info between // CMIS and SFF8636 but addresses differ From d87c7b8eac48a01ff1eeda554ec114e7e7494a8a Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Tue, 23 Jun 2026 12:24:52 +0200 Subject: [PATCH 18/36] change banks from array to slices --- internal/pkg/optic/cmis/cmis.go | 4 ++++ internal/pkg/public.go | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index d26c8a4..ca09fcb 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -203,6 +203,10 @@ func (e *ExtensionManagementStrategy) GetTunableLaserControlStatus() (*pkg.Modul var bank byte for bank = 0x00; bank <= e.state.CMISOnlyExtension.SupportedControls.MaximumBankSupported; bank += 1 { + e.state.CMISOnlyExtension.TunableLaserCtrlStatus = append( + e.state.CMISOnlyExtension.TunableLaserCtrlStatus, + pkg.CMISBankedTunableLaserControlAndStatus{}, + ) // adding banks on the go to avoid having max banks all the time _, err := GetTunableLaserControlStatus( e.state, &e.state.CMISOnlyExtension.TunableLaserCtrlStatus[bank], bank, MaximumLaneNumber, ) diff --git a/internal/pkg/public.go b/internal/pkg/public.go index a934e59..dd7d7e7 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -78,8 +78,8 @@ type CMISOnlyExtension struct { // misc feature adv tbd. // page 12 - LaserCapabilities CMISLaserCapabilitiesAdvertising `json:",omitzero"` - TunableLaserCtrlStatus [4]CMISBankedTunableLaserControlAndStatus `json:",omitzero"` // CMIS 5.3 defines 4 banks max. (0-3 32 lanes) + LaserCapabilities CMISLaserCapabilitiesAdvertising `json:",omitzero"` + TunableLaserCtrlStatus []CMISBankedTunableLaserControlAndStatus `json:",omitzero"` // CMIS 5.3 defines 4 banks max. (0-3 32 lanes) } type CMISSupportedControlsAdvertising struct { From 054e9e188bfce632b9cbcab35b373a1685a423f4 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Tue, 23 Jun 2026 13:12:13 +0200 Subject: [PATCH 19/36] fix wrong offset in u32 parsing + clearer names for 12h fields --- internal/pkg/optic/cmis/cmis.go | 6 +++--- internal/pkg/public.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index ca09fcb..b2ee416 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -454,8 +454,8 @@ func GetTunableLaserControlStatus(state *pkg.ModuleState, caps *pkg.CMISBankedTu caps.FineTuningEnableTx[i] = dumpBin[0x80+i]&pkg.CMISFineTuningEnableTxMask != 0 caps.ChannelNumberTx[i] = util.ReadBeInt16(dumpBin, 0x88+byte(2*i)) // S16 over 2 bytes - caps.FineTuningOffsetTx[i] = util.ReadBeInt16(dumpBin, 0x98+byte(2*i)) - caps.CurrentLaserFrequencyTx[i] = util.ReadBeUint32(dumpBin, 0xA8+byte(2*i)) + caps.FineTuningOffsetMhzTx[i] = util.ReadBeInt16(dumpBin, 0x98+byte(2*i)) + caps.CurrentLaserFrequencyMhzTx[i] = util.ReadBeUint32(dumpBin, 0xA8+byte(4*i)) // U32 over 4 bytes, units Mhz caps.TargetOutputPowerTx[i] = util.ReadBeInt16(dumpBin, 0xC8+byte(2*i)) @@ -496,7 +496,7 @@ func SetTunableLaserControlStatus(state *pkg.ModuleState, caps *pkg.CMISBankedTu (util.YesNoByte(caps.FineTuningEnableTx[i]) & pkg.CMISFineTuningEnableTxMask) util.WriteBeInt16(caps.ChannelNumberTx[i], dumpBin, 0x88+byte(2*i)) - util.WriteBeInt16(caps.FineTuningOffsetTx[i], dumpBin, 0x98+byte(2*i)) + util.WriteBeInt16(caps.FineTuningOffsetMhzTx[i], dumpBin, 0x98+byte(2*i)) // no write for CurrentLaserFrequency, read-only util.WriteBeInt16(caps.TargetOutputPowerTx[i], dumpBin, 0xC8+byte(2*i)) diff --git a/internal/pkg/public.go b/internal/pkg/public.go index dd7d7e7..fcfd285 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -202,9 +202,9 @@ type CMISBankedTunableLaserControlAndStatus struct { FineTuningEnableTx [8]bool // for each lane // tuning and status - ChannelNumberTx [8]int16 // S16 selected N - channel number for media lane 1-8 OF BANK - FineTuningOffsetTx [8]int16 // S16 fine-tuning frequency offset for media lane 1-8 OF BANK in offsets of 0.001 Ghz - CurrentLaserFrequencyTx [8]uint32 // U32 current frequency for media lane 1-8 OF BANK in units of 0.001 Ghz + ChannelNumberTx [8]int16 // S16 selected N - channel number for media lane 1-8 OF BANK + FineTuningOffsetMhzTx [8]int16 // S16 fine-tuning frequency offset for media lane 1-8 OF BANK in offsets of 0.001 Ghz + CurrentLaserFrequencyMhzTx [8]uint32 // U32 current frequency for media lane 1-8 OF BANK in units of 0.001 Ghz // power TargetOutputPowerTx [8]int16 // s16 programmable output power for all media lanes IN BANK units of 0.01dBm From fe3cf71bd7e6e3dc62e50a6002cee0527800aa32 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 24 Jun 2026 11:34:45 +0200 Subject: [PATCH 20/36] delete unused files --- internal/pkg/optic/dwdm.go | 82 ------------ internal/pkg/optic/i2c.go | 249 ------------------------------------- 2 files changed, 331 deletions(-) delete mode 100644 internal/pkg/optic/dwdm.go delete mode 100644 internal/pkg/optic/i2c.go diff --git a/internal/pkg/optic/dwdm.go b/internal/pkg/optic/dwdm.go deleted file mode 100644 index f6debe3..0000000 --- a/internal/pkg/optic/dwdm.go +++ /dev/null @@ -1,82 +0,0 @@ -package optic - -// This is taken from https://www.opternus.de/wissen/netzwerkprotokoll-messtechnik/dwdm-grid-saemtliche-dwdm-kanaele-mit-ihren-absoluten-werten-nach-frequenz-und-wellenlaenge - -// DWDMGridMap Hz unit -var DWDMGridMap = map[int]int{ - 1: 190.10e12, - 38: 193.80e12, - 2: 190.20e12, - 39: 193.90e12, - 3: 190.30e12, - 40: 194.00e12, - 4: 190.40e12, - 41: 194.10e12, - 5: 190.50e12, - 42: 194.20e12, - 6: 190.60e12, - 43: 194.30e12, - 7: 190.70e12, - 44: 194.40e12, - 8: 190.80e12, - 45: 194.50e12, - 9: 190.90e12, - 46: 194.60e12, - 10: 191.00e12, - 47: 194.70e12, - 11: 191.10e12, - 48: 194.80e12, - 12: 191.20e12, - 49: 194.90e12, - 13: 191.30e12, - 50: 195.00e12, - 14: 191.40e12, - 51: 195.10e12, - 15: 191.50e12, - 52: 195.20e12, - 16: 191.60e12, - 53: 195.30e12, - 17: 191.70e12, - 54: 195.40e12, - 18: 191.80e12, - 55: 195.50e12, - 19: 191.90e12, - 56: 195.60e12, - 20: 192.00e12, - 57: 195.70e12, - 21: 192.10e12, - 58: 195.80e12, - 22: 192.20e12, - 59: 195.90e12, - 23: 192.30e12, - 60: 196.00e12, - 24: 192.40e12, - 61: 196.10e12, - 25: 192.50e12, - 62: 196.20e12, - 26: 192.60e12, - 63: 196.30e12, - 27: 192.70e12, - 64: 196.40e12, - 28: 192.80e12, - 65: 196.50e12, - 29: 192.90e12, - 66: 196.60e12, - 30: 193.00e12, - 67: 196.70e12, - 31: 193.10e12, - 68: 196.80e12, - 32: 193.20e12, - 69: 196.90e12, - 33: 193.30e12, - 70: 197.00e12, - 34: 193.40e12, - 71: 197.10e12, - 35: 193.50e12, - 72: 197.20e12, - 36: 193.60e12, - 73: 197.30e12, - 37: 193.70e12, -} - -const DWDMCenterFreqHz = 193.1e12 diff --git a/internal/pkg/optic/i2c.go b/internal/pkg/optic/i2c.go deleted file mode 100644 index b1e1dde..0000000 --- a/internal/pkg/optic/i2c.go +++ /dev/null @@ -1,249 +0,0 @@ -package optic - -import ( - "encoding/binary" - "strconv" - - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" -) - -func keysByValue[K comparable, V comparable](m map[K]V, value V) *K { - for k, v := range m { - if value == v { - return &k - } - } - return nil -} - -func ToTwoComplement16(a int16) []byte { - b := make([]byte, 2) - binary.LittleEndian.PutUint16(b, uint16(a)) - return b -} - -type I2CPage12Grid int - -const ( - I2CPage12Grid3p125 = iota - I2CPage12Grid6p25 - I2CPage12Grid12p5 - I2CPage12Grid25 - I2CPage12Grid50 - I2CPage12Grid100 - I2CPage12Grid33 - I2CPage12Grid75 - I2CPage12GridRESERVED - I2CPage12GridUNAVAILABLE -) - -type DefaultMap[K comparable, V comparable] struct { - m map[K]V - d V -} - -func (m DefaultMap[K, V]) get(key K) V { - if v, ok := m.m[key]; ok { - return v - } - return m.d -} - -func (m DefaultMap[K, V]) getKey(val V) K { - for k, v := range m.m { - if v == val { - return k - } - } - panic("value not found") -} - -// I2CPage12GridMap cmis 12h reg 128-135 grid spacing -var I2CPage12GridMap = DefaultMap[byte, I2CPage12Grid]{ - m: map[byte]I2CPage12Grid{ - 0b0000_0000: I2CPage12Grid3p125, - 0b0010_0000: I2CPage12Grid12p5, - 0b0001_0000: I2CPage12Grid6p25, - 0b0011_0000: I2CPage12Grid25, - 0b0100_0000: I2CPage12Grid50, - 0b0101_0000: I2CPage12Grid100, - 0b0110_0000: I2CPage12Grid33, - 0b0111_0000: I2CPage12Grid75, - 0b1111_0000: I2CPage12GridUNAVAILABLE, - }, - d: I2CPage12GridRESERVED, -} - -// I2CPage12GridMultiplierMap Hz -var I2CPage12GridMultiplierMap = map[I2CPage12Grid]int{ - I2CPage12Grid3p125: 3.125e9, - I2CPage12Grid6p25: 6.25e9, - I2CPage12Grid12p5: 12.5e9, - I2CPage12Grid25: 25.0e9, - I2CPage12Grid50: 50.0e9, - I2CPage12Grid100: 100.0e9, - I2CPage12Grid33: 33.0e9, - I2CPage12Grid75: 75.0e9, -} - -// I2CPage12GridNameMap Ghz -var I2CPage12GridNameMap = map[I2CPage12Grid]float64{ - I2CPage12Grid3p125: 3.125, - I2CPage12Grid6p25: 6.25, - I2CPage12Grid12p5: 12.5, - I2CPage12Grid25: 25.0, - I2CPage12Grid50: 50.0, - I2CPage12Grid100: 100.0, - I2CPage12Grid33: 33.0, - I2CPage12Grid75: 75.0, -} - -type I2CPage12 struct { - Grid I2CPage12Grid - GridDisplay string - FrequencyOffset int - Frequency int - Channel *int - Status byte -} - -func InterpretPage12(dump []byte) I2CPage12 { - bitfieldGrid := dump[128] - gridSetting := I2CPage12GridMap.get(bitfieldGrid) - - u := binary.BigEndian.Uint16(dump[136:138]) - frequencyOffset := int(util.TwoComplement16(16, u)) - gridMultiplier := I2CPage12GridMultiplierMap[gridSetting] - - opticFrequency := DWDMCenterFreqHz + (frequencyOffset * gridMultiplier) - channelSetting := keysByValue(DWDMGridMap, opticFrequency) - - status := dump[231] - - return I2CPage12{ - Grid: gridSetting, - GridDisplay: strconv.FormatFloat(I2CPage12GridNameMap[gridSetting], 'f', 3, 64), - FrequencyOffset: frequencyOffset, - Frequency: opticFrequency, - Channel: channelSetting, - Status: status, - } -} - -func GetGridProgramming(gridStr float64) (page, byte int, value byte) { - grid := keysByValue(I2CPage12GridNameMap, gridStr) - newValue := I2CPage12GridMap.getKey(*grid) - return 0x12, 128, newValue -} - -func GetChannelProgramming(gridStr float64, newChannel int) (page, byte int, value byte, page2, byte2 int, value2 byte) { - gridSetting := keysByValue(I2CPage12GridNameMap, gridStr) - - targetFrequency := DWDMGridMap[newChannel] - gridMultiplier := I2CPage12GridMultiplierMap[*gridSetting] - - targetOffset := (targetFrequency - DWDMCenterFreqHz) / gridMultiplier - - sendBytes := ToTwoComplement16(int16(targetOffset)) - - return 0x12, 137, sendBytes[0], 0x12, 136, sendBytes[1] -} - -type I2CPage1E struct { - FlexTuneEnabled bool - PowerClassOverride uint8 -} - -func InterpretPage1E(dump []byte) I2CPage1E { - - flexTuneEnabled := false - if dump[200] == 0x01 { - flexTuneEnabled = true - } - - return I2CPage1E{ - FlexTuneEnabled: flexTuneEnabled, - PowerClassOverride: dump[253], - } -} - -func GetFlexTuneProgramming() (page, byte int, value byte) { - var flexTuneBit uint8 = 0b00000000 - - return 0x1E, 200, flexTuneBit -} - -func GetPowerClassProgramming(enable bool) (page, byte int, value byte) { - var powerClassBit uint8 = 0x00 - if enable { - powerClassBit = 0x01 - } - - return 0x1E, 253, powerClassBit -} - -type I2CPageB0 struct { - NominalWavelengthControlEnabled bool -} - -func InterpretPageB0(dump []byte) I2CPageB0 { - - nominalWavelengthControlEnabled := false - if dump[129] == 0x01 { - nominalWavelengthControlEnabled = true - } - - return I2CPageB0{ - NominalWavelengthControlEnabled: nominalWavelengthControlEnabled, - } -} - -func GetNominalWavelengthControlProgramming() (page, byte int, value byte) { - var enableBit uint8 = 0b00000001 - return 0xB0, 129, enableBit -} - -type I2CPage00 struct { - VendorName string - VendorPN string - VendorSN string - LowPowerMode bool -} - -func InterpretPage00(dump []byte) I2CPage00 { - - bit99Bitmask := util.Bitmask(dump[99]) - - isLowPowerMode := bit99Bitmask.Has(util.Bit1) - - return I2CPage00{ - LowPowerMode: isLowPowerMode, - VendorName: util.ParseASCIIToString(dump[148:164]), - VendorPN: util.ParseASCIIToString(dump[168:184]), - VendorSN: util.ParseASCIIToString(dump[196:212]), - } -} - -type PowerMode int - -const ( - PowerModeLowPower PowerMode = iota - PowerModeHighPower -) - -var powerModeToProgramming = map[PowerMode]uint8{ - PowerModeHighPower: 0b00000100, - PowerModeLowPower: 0b00000010, -} - -func GetPowerProgramming(power PowerMode) (page, byte int, value byte) { - var powerClassBit = powerModeToProgramming[power] - - return 0, 93, powerClassBit -} - -func GetSoftReboot() (page, byte int, value byte) { - var softRebootBit uint8 = 0b10000000 - - return 0, 93, softRebootBit -} From c45d4753054ec05a657d814d8ff3785d4b93f528 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 24 Jun 2026 11:37:32 +0200 Subject: [PATCH 21/36] add general documentation --- dwdm.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 dwdm.md diff --git a/dwdm.md b/dwdm.md new file mode 100644 index 0000000..6b0975d --- /dev/null +++ b/dwdm.md @@ -0,0 +1,68 @@ + +ITU rec G.694.1 +========================================== + +From +G.694.1 2020-10-29 version 3 +https://www.itu.int/itu-t/recommendations/rec.aspx?rec=14498 + +For C-Band, $`DWDMCenterFrequency (Hz) = 193.1 \times 10^{12}`$ + + +# STANDARD GRIDS + +## 12.5Ghz +For channel spacings of 12.5 GHz on a fiber, the allowed channel frequencies (in THz) are defined by: +$`193.1 + n × 0.0125`$ where n is a positive or negative integer including 0 + +## 25.0Ghz +For channel spacings of 25 GHz on a fiber, the allowed channel frequencies (in THz) are defined by: +$`193.1 + n × 0.025`$ where n is a positive or negative integer including 0 + +## 50.0Ghz +For channel spacings of 50 GHz on a fiber, the allowed channel frequencies (in THz) are defined by: +$`193.1 + n × 0.05`$ where n is a positive or negative integer including 0 + +## 100.0Ghz +For channel spacings of 100 GHz or more on a fiber, the allowed channel frequencies (in THz) are defined by: +$`193.1 + n × 0.1`$ where n is a positive or negative integer including 0 + +# NON-ITU GRIDS + +Cf OIForum CMIS 5.4 ver. + +## 75.0Ghz +tuning on the 75 GHz grid defined as: +$`Frequency (THz) = 193.1 + n × 0.025`$ +where n must be an integer multiple of 3. + +## 33.0Ghz +tuning on the 33 GHz grid defined as: +$`Frequency (THz) = 193.1 + n × 0.1/3`$ + +## 6.25Ghz +tuning on the 6.25 GHz grid defined as: +$`Frequency (THz) = 193.1 + n × 0.00625`$ + +## 3.125Ghz +tuning on 3.125 GHz grid defined as: +$`Frequency (THz) = 193.1 + n × 0.003125`$ + +## 150Ghz +tuning on the 150 GHz grid defined as: +$`Frequency (THz) = 193.1 + (n+3) × 0.025`$ +where n must be an integer multiple of 6. + +## 300Ghz +tuning on the 300 GHz grid defined as: +$`Frequency (THz) = 193.1 + (n-9) × 0.0125`$ +where n must be an integer multiple of 24. + + +# CHANNEL OFFSET NUMBER + +This channel offset number effectively specifies a laser frequency in terms of a (signed) frequency offset from +a nominal reference frequency at 193.1THz, in units of the grid resolution (except for the 75GHz, 150GHz +and 300GHz grids, where the offset is defined in units of a third, a sixth, or a 24th of the grid resolution, +respectively). + From a0f0a6397b9e549e7e2039bcc3e25e9806da8c23 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 24 Jun 2026 12:13:16 +0200 Subject: [PATCH 22/36] fix binary functions and add tests --- internal/pkg/optic/util/bin.go | 29 +++----- internal/pkg/optic/util/bin_test.go | 100 ++++++++++++++++++++++++++++ internal/pkg/optic/util/slice.go | 7 ++ 3 files changed, 117 insertions(+), 19 deletions(-) create mode 100644 internal/pkg/optic/util/bin_test.go create mode 100644 internal/pkg/optic/util/slice.go diff --git a/internal/pkg/optic/util/bin.go b/internal/pkg/optic/util/bin.go index d12e64f..2cb2fae 100644 --- a/internal/pkg/optic/util/bin.go +++ b/internal/pkg/optic/util/bin.go @@ -18,11 +18,11 @@ func TwoComplement16(sizeBits uint8, data uint16) (v int16) { return v } -func TwoComplement16Encode(data int16) (v uint16) { - p := math.Pow(2, 15) // 2^(N-1) - mask := int16(p) +func TwoComplement16Encode(data int16) []byte { + var buffer = []byte{0x00, 0x00} + binary.BigEndian.PutUint16(buffer, uint16(data)) // using int -> uint16 shortcut, thx go - return uint16(mask - data) + return buffer } func ReadBeUint16(bin []byte, baseOffset byte) uint16 { @@ -31,15 +31,6 @@ func ReadBeUint16(bin []byte, baseOffset byte) uint16 { return binary.BigEndian.Uint16(bin[baseOffset : baseOffset+0x02]) // go slices use half-open range, like this: [a,b[ } -func WriteBeUint16(i uint16, bin []byte, baseOffset byte) { - var buffer []byte - - binary.BigEndian.PutUint16(buffer, i) - - bin[baseOffset] = buffer[0] - bin[baseOffset] = buffer[1] -} - func ReadBeUint32(bin []byte, baseOffset byte) uint32 { return binary.BigEndian.Uint32(bin[baseOffset : baseOffset+0x04]) // go slices use half-open ranges } @@ -49,7 +40,8 @@ func ReadBeInt16(bin []byte, baseOffset byte) int16 { } func WriteBeInt16(i int16, bin []byte, baseOffset byte) { - WriteBeUint16(TwoComplement16Encode(i), bin, baseOffset) + bin[baseOffset] = TwoComplement16Encode(i)[0] + bin[baseOffset+1] = TwoComplement16Encode(i)[1] } func ReadBeInt16AndShiftBase(bin []byte, baseOffset *byte) int16 { @@ -66,12 +58,11 @@ func ReadBeUint16AndShiftBase(bin []byte, baseOffset *byte) uint16 { // BinDiffIterator will yield offset and value for each new byte that is different from original byte. old and new must // be of the exact same size. I didn't fit old and new value in there cos go only has support for up to Seq2 iter. -func BinDiffIterator(old []byte, new []byte) iter.Seq2[byte, byte] { - return func(yield func(byte, byte) bool) { - var i byte = 0x00 - for _, i = range old { +func BinDiffIterator(old []byte, new []byte) iter.Seq2[int, byte] { + return func(yield func(int, byte) bool) { + for i, v := range new { if new[i] != old[i] { - if !yield(i, new[i]) { + if !yield(i, v) { return } } diff --git a/internal/pkg/optic/util/bin_test.go b/internal/pkg/optic/util/bin_test.go new file mode 100644 index 0000000..fe6e780 --- /dev/null +++ b/internal/pkg/optic/util/bin_test.go @@ -0,0 +1,100 @@ +package util + +import ( + "encoding/binary" + "testing" +) + +const bufTemplate = "0x%2x%2x" + +func TestTwoComplement16EncodeZero(t *testing.T) { + buf := TwoComplement16Encode(0) + if buf[0] != 0x00 || buf[1] != 0x00 { + t.Errorf(bufTemplate, buf[0], buf[1]) + } +} + +func TestTwoComplement16EncodePositive(t *testing.T) { + buf := TwoComplement16Encode(32767) + if buf[0] != 0x7F || buf[1] != 0xFF { + t.Errorf(bufTemplate, buf[0], buf[1]) + } +} + +func TestTwoComplement16EncodeNegative(t *testing.T) { + buf := TwoComplement16Encode(-32768) + if buf[0] != 0x80 || buf[1] != 0x00 { + t.Errorf(bufTemplate, buf[0], buf[1]) + } +} +func TestTwoComplement16EncodeNegative2(t *testing.T) { + buf := TwoComplement16Encode(-11) + if buf[0] != 0xFF || buf[1] != 0xF5 { + t.Errorf(bufTemplate, buf[0], buf[1]) + } +} + +func TestTwoComplement16ReadZero(t *testing.T) { + buf := TwoComplement16Encode(0) + + data := binary.BigEndian.Uint16(buf) + n := TwoComplement16(16, data) + + if n != 0 { + t.Errorf(bufTemplate, buf[0], buf[1]) + } +} + +func TestTwoComplement16ReadPositive(t *testing.T) { + buf := TwoComplement16Encode(32767) + + data := binary.BigEndian.Uint16(buf) + n := TwoComplement16(16, data) + + if n != 32767 { + t.Errorf(bufTemplate, buf[0], buf[1]) + } +} + +func TestTwoComplement16ReadNegative(t *testing.T) { + buf := TwoComplement16Encode(-32768) + + data := binary.BigEndian.Uint16(buf) + n := TwoComplement16(16, data) + + if n != -32768 { + t.Errorf(bufTemplate, buf[0], buf[1]) + } +} + +func TestBinDiffIterator(t *testing.T) { + buf := []byte{ + /* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f*/ + /*00*/ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /*10*/ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /*20*/ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /*30*/ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /*40*/ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /*50*/ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /*60*/ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + bufCopy := CopySlice(buf) + + bufCopy[0x00] = 0xFF + bufCopy[0x6f] = 0xFF + + var offsets []int + var values []byte + + for offset, value := range BinDiffIterator(buf, bufCopy) { + offsets = append(offsets, offset) + values = append(values, value) + } + + if !(offsets[0] == 0x00 && values[0] == 0xFF) { + t.Errorf(bufTemplate, buf[0], buf[1]) + } + if !(offsets[1] == 0x6F && values[1] == 0xFF) { + t.Errorf(bufTemplate, buf[0], buf[1]) + } +} diff --git a/internal/pkg/optic/util/slice.go b/internal/pkg/optic/util/slice.go new file mode 100644 index 0000000..f0b956d --- /dev/null +++ b/internal/pkg/optic/util/slice.go @@ -0,0 +1,7 @@ +package util + +func CopySlice[T any](old []T) []T { + newSlice := make([]T, len(old)) + copy(newSlice, old) + return newSlice +} From 922182dbede7aeab5153b4d80890e82bc7f21d9a Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 24 Jun 2026 16:32:31 +0200 Subject: [PATCH 23/36] fix cmis implem and improvements --- cmd/optic-programmer.go | 63 +++++++----- internal/pkg/optic/cmis/cmis.go | 130 +++++++++++++++--------- internal/pkg/optic/sff8636/flexoptix.go | 11 +- internal/pkg/public.go | 86 ++++++++-------- 4 files changed, 171 insertions(+), 119 deletions(-) diff --git a/cmd/optic-programmer.go b/cmd/optic-programmer.go index 20bec1c..a17eebc 100644 --- a/cmd/optic-programmer.go +++ b/cmd/optic-programmer.go @@ -9,13 +9,14 @@ import ( "github.com/urfave/cli/v3" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/cmis" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/default" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/sff8636" connection "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick/ssh" ) +const OK = "module has processed command. check module status." + func getLoglevel(level string) slog.Level { switch strings.ToLower(level) { case "error": @@ -79,6 +80,9 @@ var allFeatureSetFactory = func(handle *connection.I2cRWHandle) *pkg.ModuleState ) } +// ActionTemplateMethod allows to factorize common action code and let caller plug callback +// module will always have basic administrative data (safe lower memory and safe page 00) fetched +// before being passed to callback with the rest of the other arguments. func ActionTemplateMethod( moduleFactory func(handle *connection.I2cRWHandle) *pkg.ModuleState, call func(module *pkg.ModuleState, context context.Context, cmd *cli.Command) error) cli.ActionFunc { @@ -107,7 +111,7 @@ func ActionTemplateMethod( } func main() { - slog.SetLogLoggerLevel(slog.LevelInfo) + slog.SetLogLoggerLevel(slog.LevelWarn) if level, ok := os.LookupEnv("LOG_LEVEL"); ok { slog.SetLogLoggerLevel(getLoglevel(level)) } @@ -157,6 +161,10 @@ func main() { context context.Context, command *cli.Command, ) error { + _, err2 := module.GetExtensionsState() + if err2 != nil { + return err2 + } bytes, err := module.ToJson() if err != nil { return err @@ -182,6 +190,7 @@ func main() { &cli.Float64Flag{ Name: "grid-spacing", Usage: "grid to use, in ghz", + Value: 100.000, }, &cli.IntFlag{ Name: "channel", @@ -189,7 +198,13 @@ func main() { }, &cli.IntFlag{ Name: "lane", - Usage: "media lane number (0-numbered)", + Usage: "media lane number", + Value: 1, + }, + &cli.IntFlag{ + Name: "bank", + Usage: "lane bank number to use", + Value: 1, }, }, Action: ActionTemplateMethod(allFeatureSetFactory, func( @@ -197,51 +212,53 @@ func main() { context context.Context, command *cli.Command, ) error { - _, err := module.Get() + _, err := module.GetExtensionsState() // non-basic, fetch extension states first. if err != nil { return err } gridSpacing := command.Float64("grid-spacing") gridSpacingStr := strconv.FormatFloat(gridSpacing, 'f', 3, 64) - channel := command.Int("channel") - lane := command.Int("lane") + channel := int16(command.Int("channel")) + lane := command.Int("lane") - 1 + bank := command.Int("bank") - 1 - if module.FlexOptixSFF8636Extension.Active { - extension := module.FlexOptixSFF8636Extension - - if !extension.LaserCapabilities.SupportedFrequencies[gridSpacingStr] { + // putting it here cos I need to capture args. + var setChannelAndGrid = func( + extension *pkg.CommonTunableLaserFields, + ) error { + if !extension.Capabilities.SupportedGridSpacings[gridSpacingStr] { panic("Module does not support this frequency.") } - targetFreq := optic.DWDMGridMap[channel] - gridMultiplier := pkg.MultiplierMap[gridSpacingStr] - targetOffset := int16((targetFreq - optic.DWDMCenterFreqHz) / gridMultiplier) - - if targetOffset > extension.LaserCapabilities.GridHighChannel[gridSpacingStr] || - targetOffset < extension.LaserCapabilities.GridLowChannel[gridSpacingStr] { + if channel > extension.Capabilities.GridHighChannel[gridSpacingStr] || + channel < extension.Capabilities.GridLowChannel[gridSpacingStr] { panic("target offset is above or below maximum frequencies for this grid.") } - // flexoptix only supports n = 0 - extension.TunableLaserCtrlStatus.GridSpacingTx[lane] = pkg.FloatGhzToCMISGridSpacing[gridSpacingStr] - extension.TunableLaserCtrlStatus.ChannelNumberTx[lane] = targetOffset + extension.CtrlStatus[bank].GridSpacingTx[lane] = pkg.FloatGhzToCMISGridSpacing[gridSpacingStr] + extension.CtrlStatus[bank].ChannelNumberTx[lane] = channel _, err := module.SetExtensionsState(module) if err != nil { return err } - bytes, err := module.ToJson() + println(OK) + + return nil + } + + if module.FlexOptixSFF8636Extension.Active { + err := setChannelAndGrid(&module.FlexOptixSFF8636Extension.TunableLaser) if err != nil { return err } - _, err = os.Stdout.Write(bytes) + } else if module.CMISOnlyExtension.Active { + err := setChannelAndGrid(&module.CMISOnlyExtension.TunableLaser) if err != nil { return err } - } else if module.CMISOnlyExtension.Active { - // pass for now } else { panic("Module does not support grid programming") } diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index b2ee416..2277dba 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -136,6 +136,10 @@ const PageSelectErrorString = "I could not write to page select register, aborti const RegisterWriteErrorString = "I could not write to arbitrary register, aborting program now." const PageOrBankNotAvailableTemplateErrorString = "Module responded that page 0x%x with bank 0x%x is not available," + " its likely I failed to correctly identify the module capabilities. Aborting program now." +const NoStagedConfigSupportErrorString = "Module has signaled that staged config change is mandatory for any change, " + + "but I do not have this capability. Aborting program now." +const FlatMemoryMapErrorString = "Module signals a flat memory map. Only lower memory and page 00 are available, " + + "and you are requesting pages above this. Aborting program now." type ManagementStrategy struct { state *pkg.ModuleState @@ -157,6 +161,21 @@ func NewCMISExtension(state *pkg.ModuleState) *ExtensionManagementStrategy { } } +func (e *ExtensionManagementStrategy) assumeHigherPagesReadable() { + if !e.state.CMISOnlyExtension.MemoryModelPaged { + panic(FlatMemoryMapErrorString) + } +} + +// assumeConfigChange ensures that module is placed in a config change state prior to write attempt. +// Requires that administrative information has already been obtained prior to write attempt +func (e *ExtensionManagementStrategy) assumeConfigChange() { + e.assumeHigherPagesReadable() + if e.state.CMISOnlyExtension.SteppedConfigOnly { + panic(NoStagedConfigSupportErrorString) + } +} + func (e *ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { _, err := e.GetSupportedControlsAdvertising() if err != nil { @@ -175,8 +194,14 @@ func (e *ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, err } func (e *ExtensionManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { - //TODO implement me - panic("implement me") + e.assumeConfigChange() + + _, err := e.SetTunableLaserControlStatus(e.state) + if err != nil { + return nil, err + } + + return e.state, nil } func (e *ExtensionManagementStrategy) ManufacturerIsCompatibleWithProtocolExtension(manufacturer string) bool { @@ -196,19 +221,17 @@ func (e *ExtensionManagementStrategy) Activate() (*pkg.ModuleState, error) { } func (e *ExtensionManagementStrategy) GetTunableLaserControlStatus() (*pkg.ModuleState, error) { - // _, err := e.state.GetAdministrativeInformation() should already have been fetched - if !e.state.CMISOnlyExtension.MemoryModelPaged { - return e.state, nil // noop - } + e.assumeHigherPagesReadable() var bank byte + e.state.CMISOnlyExtension.TunableLaser.CtrlStatus = nil for bank = 0x00; bank <= e.state.CMISOnlyExtension.SupportedControls.MaximumBankSupported; bank += 1 { - e.state.CMISOnlyExtension.TunableLaserCtrlStatus = append( - e.state.CMISOnlyExtension.TunableLaserCtrlStatus, + e.state.CMISOnlyExtension.TunableLaser.CtrlStatus = append( + e.state.CMISOnlyExtension.TunableLaser.CtrlStatus, pkg.CMISBankedTunableLaserControlAndStatus{}, ) // adding banks on the go to avoid having max banks all the time _, err := GetTunableLaserControlStatus( - e.state, &e.state.CMISOnlyExtension.TunableLaserCtrlStatus[bank], bank, MaximumLaneNumber, + e.state, &e.state.CMISOnlyExtension.TunableLaser.CtrlStatus[bank], bank, MaximumLaneNumber, ) if err != nil { return nil, err @@ -218,13 +241,24 @@ func (e *ExtensionManagementStrategy) GetTunableLaserControlStatus() (*pkg.Modul return e.state, nil } -func (e *ExtensionManagementStrategy) GetLaserCapabilitiesAdvertising() (*pkg.ModuleState, error) { - if !e.state.CMISOnlyExtension.MemoryModelPaged { - return e.state, nil // noop +func (e *ExtensionManagementStrategy) SetTunableLaserControlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { + e.assumeHigherPagesReadable() + + for i, bank := range e.state.CMISOnlyExtension.TunableLaser.CtrlStatus { + _, err := SetTunableLaserControlStatus(e.state, &bank, byte(i), MaximumLaneNumber) + if err != nil { + return nil, err + } } + return e.state, nil +} + +func (e *ExtensionManagementStrategy) GetLaserCapabilitiesAdvertising() (*pkg.ModuleState, error) { + e.assumeHigherPagesReadable() + _, err := GetLaserCapabilitiesAdvertising( - e.state, &e.state.CMISOnlyExtension.LaserCapabilities, + e.state, &e.state.CMISOnlyExtension.TunableLaser.Capabilities, ) if err != nil { return nil, err @@ -234,11 +268,7 @@ func (e *ExtensionManagementStrategy) GetLaserCapabilitiesAdvertising() (*pkg.Mo } func (e *ExtensionManagementStrategy) GetSupportedControlsAdvertising() (*pkg.ModuleState, error) { - // _, err2 := e.state.GetAdministrativeInformation() should already have been fetched - // check if page 01 is supported - if !e.state.CMISOnlyExtension.MemoryModelPaged { - return e.state, nil // noop - } + e.assumeHigherPagesReadable() dumpBin, err := e.state.GetPageBin(0x01, 0x00) if err != nil { @@ -436,9 +466,9 @@ func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, return s2.state, nil } -func (s2 *ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { - //TODO implement me - panic("implement me") +func (s2 *ManagementStrategy) SetAdministrativeInformation(_ *pkg.ModuleState) (*pkg.ModuleState, error) { + // noop + return s2.state, nil } // GetTunableLaserControlStatus maxN between 0 and 7 (channel N, used for only-channel-0 access) @@ -450,7 +480,7 @@ func GetTunableLaserControlStatus(state *pkg.ModuleState, caps *pkg.CMISBankedTu for i := 0; i <= maxN; i += 1 { caps.GridSpacingTx[i] = dumpBin[0x80+i] & pkg.CMISGridSpacingTxMask - caps.GridSpacingTxRO[i] = pkg.CMISGridSpacingToFloatGhzMap[caps.GridSpacingTx[i]] + caps.GridSpacingTxROGhz[i] = pkg.CMISGridSpacingToFloatGhzMap[caps.GridSpacingTx[i]] caps.FineTuningEnableTx[i] = dumpBin[0x80+i]&pkg.CMISFineTuningEnableTxMask != 0 caps.ChannelNumberTx[i] = util.ReadBeInt16(dumpBin, 0x88+byte(2*i)) // S16 over 2 bytes @@ -524,18 +554,18 @@ func GetLaserCapabilitiesAdvertising(state *pkg.ModuleState, caps *pkg.CMISLaser return nil, err } - caps.SupportedFrequencies = make(map[string]bool) + caps.SupportedGridSpacings = make(map[string]bool) // I really hate that Go doesn't have bitfields. - caps.SupportedFrequencies["75.000"] = dumpBin[0x80]&GridSupported75GhzMask != 0 - caps.SupportedFrequencies["33.000"] = dumpBin[0x80]&GridSupported33GhzMask != 0 - caps.SupportedFrequencies["100.000"] = dumpBin[0x80]&GridSupported100GhzMask != 0 - caps.SupportedFrequencies["50.000"] = dumpBin[0x80]&GridSupported50GhzMask != 0 - caps.SupportedFrequencies["25.000"] = dumpBin[0x80]&GridSupported25GhzMask != 0 - caps.SupportedFrequencies["12.500"] = dumpBin[0x80]&GridSupported12p5GhzMask != 0 - caps.SupportedFrequencies["6.250"] = dumpBin[0x80]&GridSupported6p25GhzMask != 0 - caps.SupportedFrequencies["3.125"] = dumpBin[0x80]&GridSupported3p125GhzMask != 0 - caps.SupportedFrequencies["150.000"] = dumpBin[0x81]&GridSupported150GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid75string] = dumpBin[0x80]&GridSupported75GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid33String] = dumpBin[0x80]&GridSupported33GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid100String] = dumpBin[0x80]&GridSupported100GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid50String] = dumpBin[0x80]&GridSupported50GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid25String] = dumpBin[0x80]&GridSupported25GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid12p5String] = dumpBin[0x80]&GridSupported12p5GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid6p250String] = dumpBin[0x80]&GridSupported6p25GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid3p125String] = dumpBin[0x80]&GridSupported3p125GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid150String] = dumpBin[0x81]&GridSupported150GhzMask != 0 caps.FineTuningSupported = dumpBin[0x81]&FineTuningSupportedMask != 0 @@ -544,32 +574,32 @@ func GetLaserCapabilitiesAdvertising(state *pkg.ModuleState, caps *pkg.CMISLaser var base byte = 0x82 // 3.125Ghz - caps.GridLowChannel["3.125"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel["3.125"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel[pkg.Grid3p125String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid3p125String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 6.25Ghz - caps.GridLowChannel["6.250"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel["6.250"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel[pkg.Grid6p250String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid6p250String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 12.5Ghz - caps.GridLowChannel["12.500"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel["12.500"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel[pkg.Grid12p5String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid12p5String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 25Ghz - caps.GridLowChannel["25.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel["25.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel[pkg.Grid25String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid25String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 50Ghz - caps.GridLowChannel["50.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel["50.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel[pkg.Grid50String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid50String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 100Ghz - caps.GridLowChannel["100.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel["100.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel[pkg.Grid100String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid100String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 33Ghz - caps.GridLowChannel["33.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel["33.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel[pkg.Grid33String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid33String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 75Ghz - caps.GridLowChannel["75.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel["75.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel[pkg.Grid75string] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid75string] = util.ReadBeInt16AndShiftBase(dumpBin, &base) // 150Ghz - caps.GridLowChannel["150.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel["150.000"] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridLowChannel[pkg.Grid150String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid150String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) base = 0xBE // skip reserved region caps.FineTuningResolution = util.ReadBeUint16AndShiftBase(dumpBin, &base) diff --git a/internal/pkg/optic/sff8636/flexoptix.go b/internal/pkg/optic/sff8636/flexoptix.go index 80db753..1da026c 100644 --- a/internal/pkg/optic/sff8636/flexoptix.go +++ b/internal/pkg/optic/sff8636/flexoptix.go @@ -20,13 +20,18 @@ func NewFlexOptixSFF8636Extension(state *pkg.ModuleState) *FlexOptixSFF8636Manag } func (f *FlexOptixSFF8636ManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { - _, err := cmis.GetLaserCapabilitiesAdvertising(f.state, &f.state.FlexOptixSFF8636Extension.LaserCapabilities) + _, err := cmis.GetLaserCapabilitiesAdvertising(f.state, &f.state.FlexOptixSFF8636Extension.TunableLaser.Capabilities) if err != nil { return nil, err } + f.state.FlexOptixSFF8636Extension.TunableLaser.CtrlStatus = nil + f.state.FlexOptixSFF8636Extension.TunableLaser.CtrlStatus = append( + f.state.FlexOptixSFF8636Extension.TunableLaser.CtrlStatus, + pkg.CMISBankedTunableLaserControlAndStatus{}, + ) _, err = cmis.GetTunableLaserControlStatus( - f.state, &f.state.FlexOptixSFF8636Extension.TunableLaserCtrlStatus, 0x00, 0, + f.state, &f.state.FlexOptixSFF8636Extension.TunableLaser.CtrlStatus[0], 0x00, 0, ) if err != nil { return nil, err @@ -39,7 +44,7 @@ func (f *FlexOptixSFF8636ManagementStrategy) SetExtensionState(s *pkg.ModuleStat // laser capabilities advertising is all-fields read-only so, no-op for this one _, err := cmis.SetTunableLaserControlStatus( - f.state, &f.state.FlexOptixSFF8636Extension.TunableLaserCtrlStatus, 0x00, 0, + f.state, &f.state.FlexOptixSFF8636Extension.TunableLaser.CtrlStatus[0], 0x00, 0, ) if err != nil { return nil, err diff --git a/internal/pkg/public.go b/internal/pkg/public.go index fcfd285..36c3ed3 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -9,20 +9,37 @@ import ( "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick/ssh" ) +const ( + Grid75string = "75.000" + Grid33String = "33.000" + Grid100String = "100.000" + Grid50String = "50.000" + Grid25String = "25.000" + Grid12p5String = "12.500" + Grid6p250String = "6.250" + Grid3p125String = "3.125" + Grid150String = "150.000" +) + // FinIsarCMISExtension client r/w interface for FinIsar specific settings // delegates concrete operations to strategy type FinIsarCMISExtension struct { Active bool } +// CommonTunableLaserFields is the common interface for tunable lasers, +type CommonTunableLaserFields struct { + Capabilities CMISLaserCapabilitiesAdvertising `json:",omitzero"` + CtrlStatus []CMISBankedTunableLaserControlAndStatus `json:",omitzero"` +} + // FlexOptixSFF8636Extension client r/w interface for FlexOptix specific settings // delegates concrete operations to strategy type FlexOptixSFF8636Extension struct { Active bool // FlexOptix has page 04h and 12h copied from CMIS - LaserCapabilities CMISLaserCapabilitiesAdvertising - TunableLaserCtrlStatus CMISBankedTunableLaserControlAndStatus // n=1 only, up to media 8 lanes support + TunableLaser CommonTunableLaserFields } // CMISOnlyExtension CMIS specific information @@ -78,8 +95,7 @@ type CMISOnlyExtension struct { // misc feature adv tbd. // page 12 - LaserCapabilities CMISLaserCapabilitiesAdvertising `json:",omitzero"` - TunableLaserCtrlStatus []CMISBankedTunableLaserControlAndStatus `json:",omitzero"` // CMIS 5.3 defines 4 banks max. (0-3 32 lanes) + TunableLaser CommonTunableLaserFields } type CMISSupportedControlsAdvertising struct { @@ -108,7 +124,7 @@ type CMISSupportedControlsAdvertising struct { type CMISLaserCapabilitiesAdvertising struct { // page 04h laser capabilities adv // is channel-based grid tuning supported on these frequencies - SupportedFrequencies map[string]bool + SupportedGridSpacings map[string]bool // is fine-tuning supported in the vicinity of an on-grid channel FineTuningSupported bool @@ -131,16 +147,16 @@ type CMISLaserCapabilitiesAdvertising struct { } const ( - CMISGridSpacing3p125Ghz = 0b0000 - CMISGridSpacing6p25Ghz = 0b0001 - CMISGridSpacing12p5Ghz = 0b0010 - CMISGridSpacing25Ghz = 0b0011 - CMISGridSpacing50Ghz = 0b0100 - CMISGridSpacing100Ghz = 0b0101 - CMISGridSpacing33Ghz = 0b0110 - CMISGridSpacing75Ghz = 0b0111 - CMISGridSpacing150Ghz = 0b1000 - CMISGridSpacingNotAvailable = 0b1111 + CMISGridSpacing3p125Ghz = 0b0000_0000 + CMISGridSpacing6p25Ghz = 0b0001_0000 + CMISGridSpacing12p5Ghz = 0b0010_0000 + CMISGridSpacing25Ghz = 0b0011_0000 + CMISGridSpacing50Ghz = 0b0100_0000 + CMISGridSpacing100Ghz = 0b0101_0000 + CMISGridSpacing33Ghz = 0b0110_0000 + CMISGridSpacing75Ghz = 0b0111_0000 + CMISGridSpacing150Ghz = 0b1000_0000 + // CMISGridSpacingNotAvailable = 0b1111_0000 ) var CMISGridSpacingToFloatGhzMap = map[byte]float64{ @@ -156,27 +172,15 @@ var CMISGridSpacingToFloatGhzMap = map[byte]float64{ } var FloatGhzToCMISGridSpacing = map[string]byte{ // cannot use float as key since not serializable - "3.125": CMISGridSpacing3p125Ghz, - "6.250": CMISGridSpacing6p25Ghz, - "12.500": CMISGridSpacing12p5Ghz, - "25.000": CMISGridSpacing25Ghz, - "50.000": CMISGridSpacing50Ghz, - "100.000": CMISGridSpacing100Ghz, - "33.000": CMISGridSpacing33Ghz, - "75.000": CMISGridSpacing75Ghz, - "150.000": CMISGridSpacing150Ghz, -} - -var MultiplierMap = map[string]int{ - "3.125": 3.125e9, - "6.250": 6.25e9, - "12.500": 12.5e9, - "25.000": 25.0e9, - "50.000": 50.0e9, - "100.000": 100.0e9, - "33.000": 33.0e9, - "75.000": 75.0e9, - "150.000": 150.0e9, + Grid3p125String: CMISGridSpacing3p125Ghz, + Grid6p250String: CMISGridSpacing6p25Ghz, + Grid12p5String: CMISGridSpacing12p5Ghz, + Grid25String: CMISGridSpacing25Ghz, + Grid50String: CMISGridSpacing50Ghz, + Grid100String: CMISGridSpacing100Ghz, + Grid33String: CMISGridSpacing33Ghz, + Grid75string: CMISGridSpacing75Ghz, + Grid150String: CMISGridSpacing150Ghz, } const ( @@ -197,8 +201,8 @@ type CMISBankedTunableLaserControlAndStatus struct { // page 12h tunable laser control and status // Grid - GridSpacingTx [8]byte // selected grid spacing of media lanes 1-8 OF BANK - GridSpacingTxRO [8]float64 // read only float64 ghz value + GridSpacingTx [8]byte `json:"-"` // selected grid spacing of media lanes 1-8 OF BANK + GridSpacingTxROGhz [8]float64 `json:"GridSpacingTx"` // read only float64 ghz value FineTuningEnableTx [8]bool // for each lane // tuning and status @@ -340,7 +344,7 @@ func (m *ModuleState) WritePageBin(page byte, bank byte, new []byte) error { } for offset, newValue := range util.BinDiffIterator(old, new) { - err := m.WritePageByteBin(page, bank, offset, newValue) + err := m.WritePageByteBin(page, bank, byte(offset), newValue) if err != nil { return err } @@ -367,10 +371,6 @@ func (m *ModuleState) Get() (*ModuleState, error) { if err != nil { return nil, err } - _, err = m.GetExtensionsState() - if err != nil { - return nil, err - } return m, nil } From 1fedb1cc8763d0b842a7f84671b53c38d627b55e Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 24 Jun 2026 16:47:05 +0200 Subject: [PATCH 24/36] renaming --- cmd/optic-programmer.go | 8 +- internal/pkg/optic/cmis/cmis.go | 66 ++++---- internal/pkg/optic/sff8636/flexoptix.go | 14 +- internal/pkg/optic/sff8636/sff8636.go | 10 +- internal/pkg/public.go | 190 ++++++++++++------------ 5 files changed, 144 insertions(+), 144 deletions(-) diff --git a/cmd/optic-programmer.go b/cmd/optic-programmer.go index a17eebc..be19a45 100644 --- a/cmd/optic-programmer.go +++ b/cmd/optic-programmer.go @@ -249,13 +249,13 @@ func main() { return nil } - if module.FlexOptixSFF8636Extension.Active { - err := setChannelAndGrid(&module.FlexOptixSFF8636Extension.TunableLaser) + if module.FlexOptixSFF8636.Active { + err := setChannelAndGrid(&module.FlexOptixSFF8636.TunableLaser) if err != nil { return err } - } else if module.CMISOnlyExtension.Active { - err := setChannelAndGrid(&module.CMISOnlyExtension.TunableLaser) + } else if module.CMIS.Active { + err := setChannelAndGrid(&module.CMIS.TunableLaser) if err != nil { return err } diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index 2277dba..fe184e6 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -162,7 +162,7 @@ func NewCMISExtension(state *pkg.ModuleState) *ExtensionManagementStrategy { } func (e *ExtensionManagementStrategy) assumeHigherPagesReadable() { - if !e.state.CMISOnlyExtension.MemoryModelPaged { + if !e.state.CMIS.MemoryModelPaged { panic(FlatMemoryMapErrorString) } } @@ -171,7 +171,7 @@ func (e *ExtensionManagementStrategy) assumeHigherPagesReadable() { // Requires that administrative information has already been obtained prior to write attempt func (e *ExtensionManagementStrategy) assumeConfigChange() { e.assumeHigherPagesReadable() - if e.state.CMISOnlyExtension.SteppedConfigOnly { + if e.state.CMIS.SteppedConfigOnly { panic(NoStagedConfigSupportErrorString) } } @@ -216,7 +216,7 @@ func (e *ExtensionManagementStrategy) SFF8024IsCompatibleWithProtocolExtension( } func (e *ExtensionManagementStrategy) Activate() (*pkg.ModuleState, error) { - e.state.CMISOnlyExtension.Active = true + e.state.CMIS.Active = true return e.state, nil } @@ -224,14 +224,14 @@ func (e *ExtensionManagementStrategy) GetTunableLaserControlStatus() (*pkg.Modul e.assumeHigherPagesReadable() var bank byte - e.state.CMISOnlyExtension.TunableLaser.CtrlStatus = nil - for bank = 0x00; bank <= e.state.CMISOnlyExtension.SupportedControls.MaximumBankSupported; bank += 1 { - e.state.CMISOnlyExtension.TunableLaser.CtrlStatus = append( - e.state.CMISOnlyExtension.TunableLaser.CtrlStatus, + e.state.CMIS.TunableLaser.CtrlStatus = nil + for bank = 0x00; bank <= e.state.CMIS.SupportedControls.MaximumBankSupported; bank += 1 { + e.state.CMIS.TunableLaser.CtrlStatus = append( + e.state.CMIS.TunableLaser.CtrlStatus, pkg.CMISBankedTunableLaserControlAndStatus{}, ) // adding banks on the go to avoid having max banks all the time _, err := GetTunableLaserControlStatus( - e.state, &e.state.CMISOnlyExtension.TunableLaser.CtrlStatus[bank], bank, MaximumLaneNumber, + e.state, &e.state.CMIS.TunableLaser.CtrlStatus[bank], bank, MaximumLaneNumber, ) if err != nil { return nil, err @@ -244,7 +244,7 @@ func (e *ExtensionManagementStrategy) GetTunableLaserControlStatus() (*pkg.Modul func (e *ExtensionManagementStrategy) SetTunableLaserControlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { e.assumeHigherPagesReadable() - for i, bank := range e.state.CMISOnlyExtension.TunableLaser.CtrlStatus { + for i, bank := range e.state.CMIS.TunableLaser.CtrlStatus { _, err := SetTunableLaserControlStatus(e.state, &bank, byte(i), MaximumLaneNumber) if err != nil { return nil, err @@ -258,7 +258,7 @@ func (e *ExtensionManagementStrategy) GetLaserCapabilitiesAdvertising() (*pkg.Mo e.assumeHigherPagesReadable() _, err := GetLaserCapabilitiesAdvertising( - e.state, &e.state.CMISOnlyExtension.TunableLaser.Capabilities, + e.state, &e.state.CMIS.TunableLaser.Capabilities, ) if err != nil { return nil, err @@ -275,7 +275,7 @@ func (e *ExtensionManagementStrategy) GetSupportedControlsAdvertising() (*pkg.Mo return nil, err } - caps := &e.state.CMISOnlyExtension.SupportedControls + caps := &e.state.CMIS.SupportedControls caps.ModuleInactiveFirmwareMajorRevision = dumpBin[0x80] caps.ModuleInactiveFirmwareMinorRevision = dumpBin[0x81] @@ -428,40 +428,40 @@ func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, s2.state.ManagementProtocol = "cmis" // lower mem - s2.state.CMISOnlyExtension.MemoryModelPaged = dumpBin[0x02]&MemoryModelMask == 0 // 0 is paged memory, 1 is flat - s2.state.CMISOnlyExtension.SteppedConfigOnly = dumpBin[0x02]&SteppedConfigOnlyMask == 0 - s2.state.CMISOnlyExtension.I2CMciMaxSpeedKhz = I2CMciMaxSpeedToKhz[dumpBin[0x02]&I2CMciMaxSpeedMask] - s2.state.CMISOnlyExtension.SPIMciMaxSpeedKhz = SPIMciMaxSpeedToKhz[dumpBin[0x02]&SPIMCIMaxSpeedMask] + s2.state.CMIS.MemoryModelPaged = dumpBin[0x02]&MemoryModelMask == 0 // 0 is paged memory, 1 is flat + s2.state.CMIS.SteppedConfigOnly = dumpBin[0x02]&SteppedConfigOnlyMask == 0 + s2.state.CMIS.I2CMciMaxSpeedKhz = I2CMciMaxSpeedToKhz[dumpBin[0x02]&I2CMciMaxSpeedMask] + s2.state.CMIS.SPIMciMaxSpeedKhz = SPIMciMaxSpeedToKhz[dumpBin[0x02]&SPIMCIMaxSpeedMask] AutoCommissioning := dumpBin[0x02] & AutoCommissioningMask - s2.state.CMISOnlyExtension.AutoCommissioningNone = AutoCommissioning == 0b0000 - s2.state.CMISOnlyExtension.AutoCommissioningRegular = AutoCommissioning == 0b0001 - s2.state.CMISOnlyExtension.AutoCommissioningHot = AutoCommissioning == 0b0010 // 0x11 is reserved + s2.state.CMIS.AutoCommissioningNone = AutoCommissioning == 0b0000 + s2.state.CMIS.AutoCommissioningRegular = AutoCommissioning == 0b0001 + s2.state.CMIS.AutoCommissioningHot = AutoCommissioning == 0b0010 // 0x11 is reserved // page 00 s2.state.VendorName = util.ParseASCIIToString(dumpBin[0x81:0x90]) - s2.state.CMISOnlyExtension.VendorOUI = dumpBin[0x91:0x93] + s2.state.CMIS.VendorOUI = dumpBin[0x91:0x93] s2.state.VendorPartNumber = util.ParseASCIIToString(dumpBin[0x94:0xA3]) s2.state.VendorPartRevision = util.ParseASCIIToString(dumpBin[0xA4:0xA5]) s2.state.VendorSerialNumber = util.ParseASCIIToString(dumpBin[0xA6:0xB5]) - s2.state.CMISOnlyExtension.DateCode = util.ParseASCIIToString(dumpBin[0xB6:0xBD]) - s2.state.CMISOnlyExtension.CLEICode = util.ParseASCIIToString(dumpBin[0xBE:0xC7]) - s2.state.CMISOnlyExtension.PowerClass = PowerClassToInt[dumpBin[0xC8]&ModulePowerClassMask] - s2.state.CMISOnlyExtension.MaxPowerWatts = 0.25 * float64(dumpBin[0xC9]) // byte is interpreted as uint8. unit is ceil of quarter-watts + s2.state.CMIS.DateCode = util.ParseASCIIToString(dumpBin[0xB6:0xBD]) + s2.state.CMIS.CLEICode = util.ParseASCIIToString(dumpBin[0xBE:0xC7]) + s2.state.CMIS.PowerClass = PowerClassToInt[dumpBin[0xC8]&ModulePowerClassMask] + s2.state.CMIS.MaxPowerWatts = 0.25 * float64(dumpBin[0xC9]) // byte is interpreted as uint8. unit is ceil of quarter-watts - s2.state.CMISOnlyExtension.SupportedMediaLanes = make(map[int]bool) + s2.state.CMIS.SupportedMediaLanes = make(map[int]bool) // fetching media lane support per lane var mask byte = 0b1000_0000 for i := 8; i >= 1; i -= 1 { // MSB countdown - s2.state.CMISOnlyExtension.SupportedMediaLanes[i] = dumpBin[0xD2]&mask == 0 // supported == 0, unsupported == 1 + s2.state.CMIS.SupportedMediaLanes[i] = dumpBin[0xD2]&mask == 0 // supported == 0, unsupported == 1 mask >>= 1 } - s2.state.CMISOnlyExtension.FarEndDetachableMedia = dumpBin[0xD3]&FarEndConfigurationMask == 0b0000_0000 - s2.state.CMISOnlyExtension.FarEnd1LaneModule = dumpBin[0xD3]&FarEndConfigurationMask == 0b0000_0001 - s2.state.CMISOnlyExtension.FarEnd2LanesModule = dumpBin[0xD3]&FarEndConfigurationMask == 0b000_0110 - s2.state.CMISOnlyExtension.FarEnd4LanesModule = dumpBin[0xD3]&FarEndConfigurationMask == 0b000_0011 - s2.state.CMISOnlyExtension.FarEnd8LanesModule = dumpBin[0xD3]&FarEndConfigurationMask == 0b000_0010 - s2.state.CMISOnlyExtension.FarEnd16LanesModule = dumpBin[0xD3]&FarEndConfigurationMask == 0b001_1011 - s2.state.CMISOnlyExtension.MediaInterface = MediaInterfaceToStr[dumpBin[0xD4]] + s2.state.CMIS.FarEndDetachableMedia = dumpBin[0xD3]&FarEndConfigurationMask == 0b0000_0000 + s2.state.CMIS.FarEnd1LaneModule = dumpBin[0xD3]&FarEndConfigurationMask == 0b0000_0001 + s2.state.CMIS.FarEnd2LanesModule = dumpBin[0xD3]&FarEndConfigurationMask == 0b000_0110 + s2.state.CMIS.FarEnd4LanesModule = dumpBin[0xD3]&FarEndConfigurationMask == 0b000_0011 + s2.state.CMIS.FarEnd8LanesModule = dumpBin[0xD3]&FarEndConfigurationMask == 0b000_0010 + s2.state.CMIS.FarEnd16LanesModule = dumpBin[0xD3]&FarEndConfigurationMask == 0b001_1011 + s2.state.CMIS.MediaInterfaceDescription = MediaInterfaceToStr[dumpBin[0xD4]] return s2.state, nil } @@ -498,7 +498,7 @@ func GetTunableLaserControlStatus(state *pkg.ModuleState, caps *pkg.CMISBankedTu caps.TargetOutputPowerOORFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISTargetOutputPowerOORFlagTxMask != 0 caps.FineTuningOutOfRangeFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISFineTuningOutOfRangeFlagTxMask != 0 caps.TuningNotAcceptedMaskTx[i] = dumpBin[0xE7+i]&pkg.CMISTuningNotAcceptedFlagTxMask != 0 - caps.InvalidChannelNumberFLagTx[i] = dumpBin[0xE7+i]&pkg.CMISInvalidChannelNumberFlagTxMask != 0 + caps.InvalidChannelNumberFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISInvalidChannelNumberFlagTxMask != 0 caps.WavelengthUnlockedFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISWavelengthUnlockedFlagTxMask != 0 caps.TuningCompleteFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISTuningCompleteFlagTxMask != 0 diff --git a/internal/pkg/optic/sff8636/flexoptix.go b/internal/pkg/optic/sff8636/flexoptix.go index 1da026c..4bebfd7 100644 --- a/internal/pkg/optic/sff8636/flexoptix.go +++ b/internal/pkg/optic/sff8636/flexoptix.go @@ -20,18 +20,18 @@ func NewFlexOptixSFF8636Extension(state *pkg.ModuleState) *FlexOptixSFF8636Manag } func (f *FlexOptixSFF8636ManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { - _, err := cmis.GetLaserCapabilitiesAdvertising(f.state, &f.state.FlexOptixSFF8636Extension.TunableLaser.Capabilities) + _, err := cmis.GetLaserCapabilitiesAdvertising(f.state, &f.state.FlexOptixSFF8636.TunableLaser.Capabilities) if err != nil { return nil, err } - f.state.FlexOptixSFF8636Extension.TunableLaser.CtrlStatus = nil - f.state.FlexOptixSFF8636Extension.TunableLaser.CtrlStatus = append( - f.state.FlexOptixSFF8636Extension.TunableLaser.CtrlStatus, + f.state.FlexOptixSFF8636.TunableLaser.CtrlStatus = nil + f.state.FlexOptixSFF8636.TunableLaser.CtrlStatus = append( + f.state.FlexOptixSFF8636.TunableLaser.CtrlStatus, pkg.CMISBankedTunableLaserControlAndStatus{}, ) _, err = cmis.GetTunableLaserControlStatus( - f.state, &f.state.FlexOptixSFF8636Extension.TunableLaser.CtrlStatus[0], 0x00, 0, + f.state, &f.state.FlexOptixSFF8636.TunableLaser.CtrlStatus[0], 0x00, 0, ) if err != nil { return nil, err @@ -44,7 +44,7 @@ func (f *FlexOptixSFF8636ManagementStrategy) SetExtensionState(s *pkg.ModuleStat // laser capabilities advertising is all-fields read-only so, no-op for this one _, err := cmis.SetTunableLaserControlStatus( - f.state, &f.state.FlexOptixSFF8636Extension.TunableLaser.CtrlStatus[0], 0x00, 0, + f.state, &f.state.FlexOptixSFF8636.TunableLaser.CtrlStatus[0], 0x00, 0, ) if err != nil { return nil, err @@ -62,6 +62,6 @@ func (f *FlexOptixSFF8636ManagementStrategy) SFF8024IsCompatibleWithProtocolExte } func (f *FlexOptixSFF8636ManagementStrategy) Activate() (*pkg.ModuleState, error) { - f.state.FlexOptixSFF8636Extension.Active = true + f.state.FlexOptixSFF8636.Active = true return f.state, nil } diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index 692e35c..080c695 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -93,7 +93,7 @@ func (s2 *ExtensionManagementStrategy) SFF8024IsCompatibleWithProtocolExtension( } func (s2 *ExtensionManagementStrategy) Activate() (*pkg.ModuleState, error) { - s2.state.SFF8636OnlyExtension.Active = true + s2.state.SFF8636.Active = true return s2.state, nil } @@ -174,10 +174,10 @@ func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, s2.state.SFF8024Identifier = bin[0x00] s2.state.SFF8024Revision = bin[0x01] s2.state.SoftwareReset = bin[0x5D]&SoftwareResetMask == SoftwareResetMask - s2.state.SFF8636OnlyExtension.EnableHighPowerClass8 = bin[0x5D]&EnableHighPowerClass8Mask == EnableHighPowerClass8Mask - s2.state.SFF8636OnlyExtension.EnableHighPowerClass57 = bin[0x5D]&EnableHighPowerClass57Mask == EnableHighPowerClass57Mask - s2.state.SFF8636OnlyExtension.LowPwrRequestSW = bin[0x5D]&LowPwrRequestSWMask == LowPwrRequestSWMask - s2.state.SFF8636OnlyExtension.LowPwrOverride = bin[0x5D]&LowPwrOverrideMask == LowPwrOverrideMask + s2.state.SFF8636.EnableHighPowerClass8 = bin[0x5D]&EnableHighPowerClass8Mask == EnableHighPowerClass8Mask + s2.state.SFF8636.EnableHighPowerClass57 = bin[0x5D]&EnableHighPowerClass57Mask == EnableHighPowerClass57Mask + s2.state.SFF8636.LowPwrRequestSW = bin[0x5D]&LowPwrRequestSWMask == LowPwrRequestSWMask + s2.state.SFF8636.LowPwrOverride = bin[0x5D]&LowPwrOverrideMask == LowPwrOverrideMask // page 0x00 s2.state.VendorName = util.ParseASCIIToString(bin[0x94:0xA3]) diff --git a/internal/pkg/public.go b/internal/pkg/public.go index 36c3ed3..073fa45 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -24,43 +24,43 @@ const ( // FinIsarCMISExtension client r/w interface for FinIsar specific settings // delegates concrete operations to strategy type FinIsarCMISExtension struct { - Active bool + Active bool `json:"active"` } // CommonTunableLaserFields is the common interface for tunable lasers, type CommonTunableLaserFields struct { - Capabilities CMISLaserCapabilitiesAdvertising `json:",omitzero"` - CtrlStatus []CMISBankedTunableLaserControlAndStatus `json:",omitzero"` + Capabilities CMISLaserCapabilitiesAdvertising `json:"capabilities,omitzero"` + CtrlStatus []CMISBankedTunableLaserControlAndStatus `json:"control_status,omitzero"` } // FlexOptixSFF8636Extension client r/w interface for FlexOptix specific settings // delegates concrete operations to strategy type FlexOptixSFF8636Extension struct { - Active bool + Active bool `json:"active"` // FlexOptix has page 04h and 12h copied from CMIS - TunableLaser CommonTunableLaserFields + TunableLaser CommonTunableLaserFields `json:"tunable_laser"` } // CMISOnlyExtension CMIS specific information type CMISOnlyExtension struct { - Active bool + Active bool `json:"active"` // lower mem - MemoryModelPaged bool // MemoryModelPaged is true if paged, false if flat (flat is lower + page 0x00 only) - SteppedConfigOnly bool // SteppedConfigOnly true if all types of reconfiguration (step by step hot + regular), false if step by step / none autocomm. - I2CMciMaxSpeedKhz int // I2CMciMaxSpeedKhz Maximum I2C MCI interface speed in Khz - SPIMciMaxSpeedKhz int // SPIMciMaxSpeedKhz Maximum MCI SPI interface speed in Khz - AutoCommissioningNone bool // AutoCommissioningNone true if no auto-commissioning is supported - AutoCommissioningRegular bool // AutoCommissioningRegular true if regular auto-commissioning is supported (Affects ApplyDPInit) - AutoCommissioningHot bool // AutoCommissioningHot true if only hot auto-commissioning is supported (Affects ApplyImmediate) + MemoryModelPaged bool `json:"memory_model_paged"` // MemoryModelPaged is true if paged, false if flat (flat is lower + page 0x00 only) + SteppedConfigOnly bool `json:"stepped_config_only"` // SteppedConfigOnly true if all types of reconfiguration (step by step hot + regular), false if step by step / none autocomm. + I2CMciMaxSpeedKhz int `json:"i2c_mci_max_speed_khz"` // I2CMciMaxSpeedKhz Maximum I2C MCI interface speed in Khz + SPIMciMaxSpeedKhz int `json:"spi_mci_max_speed_khz"` // SPIMciMaxSpeedKhz Maximum MCI SPI interface speed in Khz + AutoCommissioningNone bool `json:"auto_commissioning_none"` // AutoCommissioningNone true if no auto-commissioning is supported + AutoCommissioningRegular bool `json:"auto_commissioning_regular"` // AutoCommissioningRegular true if regular auto-commissioning is supported (Affects ApplyDPInit) + AutoCommissioningHot bool `json:"auto_commissioning_hot"` // AutoCommissioningHot true if only hot auto-commissioning is supported (Affects ApplyImmediate) // page 00, all - VendorOUI []byte - DateCode string - CLEICode string - PowerClass int - MaxPowerWatts float64 // MaxPowerWatts is in multiples of 0.25W, ceil. + VendorOUI []byte `json:"vendor_oui"` + DateCode string `json:"date_code"` + CLEICode string `json:"clei_code"` + PowerClass int `json:"power_class"` + MaxPowerWatts float64 `json:"max_power_watts"` // MaxPowerWatts is in multiples of 0.25W, ceil. // page 00, copper and active // CableAssemblyLengthMeters uint @@ -72,17 +72,17 @@ type CMISOnlyExtension struct { // AttenuationAt53p1Ghz uint8 // page 00, cont. - SupportedMediaLanes map[int]bool - FarEndDetachableMedia bool - FarEnd1LaneModule bool - FarEnd2LanesModule bool - FarEnd4LanesModule bool - FarEnd8LanesModule bool - FarEnd16LanesModule bool - MediaInterface string + SupportedMediaLanes map[int]bool `json:"supported_media_lanes"` + FarEndDetachableMedia bool `json:"far_end_detachable_media"` + FarEnd1LaneModule bool `json:"far_end_1_lane_module"` + FarEnd2LanesModule bool `json:"far_end_2_lanes_module"` + FarEnd4LanesModule bool `json:"far_end_4_lanes_module"` + FarEnd8LanesModule bool `json:"far_end_8_lanes_module"` + FarEnd16LanesModule bool `json:"far_end_16_lanes_module"` + MediaInterfaceDescription string `json:"media_interface_description"` // page 01, optional - SupportedControls CMISSupportedControlsAdvertising `json:",omitzero"` + SupportedControls CMISSupportedControlsAdvertising `json:"supported_controls,omitzero"` // supported flags tbd. // supported monitors tbd. @@ -95,55 +95,55 @@ type CMISOnlyExtension struct { // misc feature adv tbd. // page 12 - TunableLaser CommonTunableLaserFields + TunableLaser CommonTunableLaserFields `json:"tunable_laser"` } type CMISSupportedControlsAdvertising struct { - ModuleInactiveFirmwareMajorRevision uint8 - ModuleInactiveFirmwareMinorRevision uint8 - ModuleHardwareMajorRevision uint8 - ModuleHardwareMinorRevision uint8 + ModuleInactiveFirmwareMajorRevision uint8 `json:"module_inactive_firmware_major_revision"` + ModuleInactiveFirmwareMinorRevision uint8 `json:"module_inactive_firmware_minor_revision"` + ModuleHardwareMajorRevision uint8 `json:"module_hardware_major_revision"` + ModuleHardwareMinorRevision uint8 `json:"module_hardware_minor_revision"` // link length support tbd. - NominalWavelengthNm float64 // NominalWavelengthNm at room temp. - WavelengthToleranceNm float64 // WavelengthToleranceNm worst case tolerance around nominal - MaximumBankSupported byte // MaximumBankSupported upper bank limit (0, 0-1, 0-3 for 8, 16, 32 lanes respectively) + NominalWavelengthNm float64 `json:"nominal_wavelength_nm"` // NominalWavelengthNm at room temp. + WavelengthToleranceNm float64 `json:"wavelength_tolerance_nm"` // WavelengthToleranceNm worst case tolerance around nominal + MaximumBankSupported byte `json:"maximum_bank_supported"` // MaximumBankSupported upper bank limit (0, 0-1, 0-3 for 8, 16, 32 lanes respectively) // module characteristics adv. tbd. - WavelengthIsControllable bool - TransmitterIsTunable bool - SquelchMethodTx byte - ForcedSquelchTxSupported bool - AutoSquelchDisableTxSupported bool - AutoSquelchDisableRxSupported bool - OutputDisableTxSupported bool - OutputDisableRxSupported bool - InputPolarityFlipTxSupported bool - OutputPolarityFlipRxSupported bool - BankBroadcastSupported bool + WavelengthIsControllable bool `json:"wavelength_is_controllable"` + TransmitterIsTunable bool `json:"transmitter_is_tunable"` + SquelchMethodTx byte `json:"squelch_method_tx"` + ForcedSquelchTxSupported bool `json:"forced_squelch_tx_supported"` + AutoSquelchDisableTxSupported bool `json:"auto_squelch_disable_tx_supported"` + AutoSquelchDisableRxSupported bool `json:"auto_squelch_disable_rx_supported"` + OutputDisableTxSupported bool `json:"output_disable_tx_supported"` + OutputDisableRxSupported bool `json:"output_disable_rx_supported"` + InputPolarityFlipTxSupported bool `json:"input_polarity_flip_tx_supported"` + OutputPolarityFlipRxSupported bool `json:"output_polarity_flip_rx_supported"` + BankBroadcastSupported bool `json:"bank_broadcast_supported"` } type CMISLaserCapabilitiesAdvertising struct { // page 04h laser capabilities adv // is channel-based grid tuning supported on these frequencies - SupportedGridSpacings map[string]bool + SupportedGridSpacings map[string]bool `json:"supported_grid_spacings"` // is fine-tuning supported in the vicinity of an on-grid channel - FineTuningSupported bool + FineTuningSupported bool `json:"fine_tuning_supported"` // S16 encoded lowest N for spacing for each freq - GridLowChannel map[string]int16 + GridLowChannel map[string]int16 `json:"grid_low_channel"` // S16 encoded higher N for spacing for each freq - GridHighChannel map[string]int16 + GridHighChannel map[string]int16 `json:"grid_high_channel"` // fine-tuning res, 0.001 Ghz increments - FineTuningResolution uint16 - FineTuningLowOffset int16 - FineTuningHighOffset int16 + FineTuningResolution uint16 `json:"fine_tuning_resolution"` + FineTuningLowOffset int16 `json:"fine_tuning_low_offset"` + FineTuningHighOffset int16 `json:"fine_tuning_high_offset"` // programmable output power - ProgOutputPowerPerLaneSupported bool // per-lane programmable y/n - ProgOutputPowerMin int16 // 0.001 dBm increments min power - ProgOutputPowerMax int16 // 0.001 dBm increments max power + ProgOutputPowerPerLaneSupported bool `json:"prog_output_power_per_lane_supported"` // per-lane programmable y/n + ProgOutputPowerMin int16 `json:"prog_output_power_min"` // 0.001 dBm increments min power + ProgOutputPowerMax int16 `json:"prog_output_power_max"` // 0.001 dBm increments max power } const ( @@ -201,49 +201,49 @@ type CMISBankedTunableLaserControlAndStatus struct { // page 12h tunable laser control and status // Grid - GridSpacingTx [8]byte `json:"-"` // selected grid spacing of media lanes 1-8 OF BANK - GridSpacingTxROGhz [8]float64 `json:"GridSpacingTx"` // read only float64 ghz value - FineTuningEnableTx [8]bool // for each lane + GridSpacingTx [8]byte `json:"-"` // selected grid spacing of media lanes 1-8 OF BANK + GridSpacingTxROGhz [8]float64 `json:"grid_spacing_tx"` // read only float64 ghz value + FineTuningEnableTx [8]bool `json:"fine_tuning_enable_tx"` // for each lane // tuning and status - ChannelNumberTx [8]int16 // S16 selected N - channel number for media lane 1-8 OF BANK - FineTuningOffsetMhzTx [8]int16 // S16 fine-tuning frequency offset for media lane 1-8 OF BANK in offsets of 0.001 Ghz - CurrentLaserFrequencyMhzTx [8]uint32 // U32 current frequency for media lane 1-8 OF BANK in units of 0.001 Ghz + ChannelNumberTx [8]int16 `json:"channel_number_tx"` // S16 selected N - channel number for media lane 1-8 OF BANK + FineTuningOffsetMhzTx [8]int16 `json:"fine_tuning_offset_mhz_tx"` // S16 fine-tuning frequency offset for media lane 1-8 OF BANK in offsets of 0.001 Ghz + CurrentLaserFrequencyMhzTx [8]uint32 `json:"current_laser_frequency_mhz_tx"` // U32 current frequency for media lane 1-8 OF BANK in units of 0.001 Ghz // power - TargetOutputPowerTx [8]int16 // s16 programmable output power for all media lanes IN BANK units of 0.01dBm + TargetOutputPowerTx [8]int16 `json:"target_output_power_tx"` // s16 programmable output power for all media lanes IN BANK units of 0.01dBm // lock status - TuningInProgressTx [8]bool // whether tuning is in progress on all media lanes IN BANK - WaveLengthUnlockStatus [8]bool // unlocked status indication for laser on all media lanes IN BANK + TuningInProgressTx [8]bool `json:"tuning_in_progress_tx"` // whether tuning is in progress on all media lanes IN BANK + WaveLengthUnlockStatus [8]bool `json:"wave_length_unlock_status"` // unlocked status indication for laser on all media lanes IN BANK // latched flags, cleared on module read - LaserTuningFlagSummaryTx [8]bool - TargetOutputPowerOORFlagTx [8]bool // indicates whether target output power value was entered for media lane - FineTuningOutOfRangeFlagTx [8]bool // indicates whether fine-tuning target value was outside range - TuningNotAcceptedFlagTx [8]bool // indicates a failed tuning operation for media lane - InvalidChannelNumberFLagTx [8]bool // required channel number not in advertised range of spacing - WavelengthUnlockedFlagTx [8]bool - TuningCompleteFlagTx [8]bool // tuning has been completed y/n + LaserTuningFlagSummaryTx [8]bool `json:"laser_tuning_flag_summary_tx"` + TargetOutputPowerOORFlagTx [8]bool `json:"target_output_power_oor_flag_tx"` // indicates whether target output power value was entered for media lane + FineTuningOutOfRangeFlagTx [8]bool `json:"fine_tuning_out_of_range_flag_tx"` // indicates whether fine-tuning target value was outside range + TuningNotAcceptedFlagTx [8]bool `json:"tuning_not_accepted_flag_tx"` // indicates a failed tuning operation for media lane + InvalidChannelNumberFlagTx [8]bool `json:"invalid_channel_number_flag_tx"` // required channel number not in advertised range of spacing + WavelengthUnlockedFlagTx [8]bool `json:"wavelength_unlocked_flag_tx"` + TuningCompleteFlagTx [8]bool `json:"tuning_complete_flag_tx"` // tuning has been completed y/n // masks for interrupt generation suppression - TargetOutputPowerOORMaskTx [8]bool - FineTuningPowerOutOfRangeMaskTx [8]bool - TuningNotAcceptedMaskTx [8]bool - InvalidChannelMaskTx [8]bool - WavelengthUnlockedMaskTx [8]bool - TuningCompleteMaskTx [8]bool + TargetOutputPowerOORMaskTx [8]bool `json:"target_output_power_oor_mask_tx"` + FineTuningPowerOutOfRangeMaskTx [8]bool `json:"fine_tuning_power_out_of_range_mask_tx"` + TuningNotAcceptedMaskTx [8]bool `json:"tuning_not_accepted_mask_tx"` + InvalidChannelMaskTx [8]bool `json:"invalid_channel_mask_tx"` + WavelengthUnlockedMaskTx [8]bool `json:"wavelength_unlocked_mask_tx"` + TuningCompleteMaskTx [8]bool `json:"tuning_complete_mask_tx"` } // SFF8636OnlyExtension SFF8636 specific information type SFF8636OnlyExtension struct { - Active bool + Active bool `json:"active"` // lower mem - EnableHighPowerClass8 bool - EnableHighPowerClass57 bool - LowPwrRequestSW bool - LowPwrOverride bool + EnableHighPowerClass8 bool `json:"enable_high_power_class_8"` + EnableHighPowerClass57 bool `json:"enable_high_power_class_57"` + LowPwrRequestSW bool `json:"low_pwr_request_sw"` + LowPwrOverride bool `json:"low_pwr_override"` } // ModuleState is data exchange interface - delegates to strategy @@ -255,23 +255,23 @@ type ModuleState struct { mgmtProtoExtensionsConcreteStrategies []ConcreteExtensionManagementStrategy handle *connection.I2cRWHandle // private pointer to connection handle. - FinIsarCMISExtension FinIsarCMISExtension `json:",omitzero"` - FlexOptixSFF8636Extension FlexOptixSFF8636Extension `json:",omitzero"` - SFF8636OnlyExtension SFF8636OnlyExtension `json:",omitzero"` - CMISOnlyExtension CMISOnlyExtension `json:",omitzero"` + FinIsarCMIS FinIsarCMISExtension `json:"finisar_cmis,omitzero"` + FlexOptixSFF8636 FlexOptixSFF8636Extension `json:"flexoptix_sff8636,omitzero"` + SFF8636 SFF8636OnlyExtension `json:"sff8636,omitzero"` + CMIS CMISOnlyExtension `json:"cmis,omitzero"` // lower mem region - ManagementProtocol string - SFF8024Identifier uint8 // SFF8024Identifier lower mem public read-only sff8024 id field - SFF8024Revision uint8 // SFF8024Revision lower mem public read-only sff8024 revision id field - SoftwareReset bool + ManagementProtocol string `json:"management_protocol"` + SFF8024Identifier uint8 `json:"sff_8024_identifier"` // SFF8024Identifier lower mem public read-only sff8024 id field + SFF8024Revision uint8 `json:"sff_8024_revision"` // SFF8024Revision lower mem public read-only sff8024 revision id field + SoftwareReset bool `json:"software_reset"` // page 00 region, common info between // CMIS and SFF8636 but addresses differ - VendorName string - VendorPartNumber string - VendorPartRevision string - VendorSerialNumber string + VendorName string `json:"vendor_name"` + VendorPartNumber string `json:"vendor_part_number"` + VendorPartRevision string `json:"vendor_part_revision"` + VendorSerialNumber string `json:"vendor_serial_number"` } func NewModuleState( From 6b2d720eb5aace9e575b4bd686ffbd7a03b84fa5 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 24 Jun 2026 17:50:55 +0200 Subject: [PATCH 25/36] delete unused files --- internal/pkg/optic/cmis/finisar.go | 1 - internal/pkg/optic/cmis/flexoptix.go | 1 - internal/pkg/optic/sff8636/finisar.go | 1 - internal/pkg/optic/util/bitmask.go | 19 ----- internal/pkg/routines/read_actions.go | 45 ------------ internal/pkg/routines/routines.go | 102 -------------------------- 6 files changed, 169 deletions(-) delete mode 100644 internal/pkg/optic/cmis/finisar.go delete mode 100644 internal/pkg/optic/cmis/flexoptix.go delete mode 100644 internal/pkg/optic/sff8636/finisar.go delete mode 100644 internal/pkg/optic/util/bitmask.go delete mode 100644 internal/pkg/routines/read_actions.go delete mode 100644 internal/pkg/routines/routines.go diff --git a/internal/pkg/optic/cmis/finisar.go b/internal/pkg/optic/cmis/finisar.go deleted file mode 100644 index 8480d15..0000000 --- a/internal/pkg/optic/cmis/finisar.go +++ /dev/null @@ -1 +0,0 @@ -package cmis diff --git a/internal/pkg/optic/cmis/flexoptix.go b/internal/pkg/optic/cmis/flexoptix.go deleted file mode 100644 index 8480d15..0000000 --- a/internal/pkg/optic/cmis/flexoptix.go +++ /dev/null @@ -1 +0,0 @@ -package cmis diff --git a/internal/pkg/optic/sff8636/finisar.go b/internal/pkg/optic/sff8636/finisar.go deleted file mode 100644 index c40351c..0000000 --- a/internal/pkg/optic/sff8636/finisar.go +++ /dev/null @@ -1 +0,0 @@ -package sff8636 diff --git a/internal/pkg/optic/util/bitmask.go b/internal/pkg/optic/util/bitmask.go deleted file mode 100644 index f854799..0000000 --- a/internal/pkg/optic/util/bitmask.go +++ /dev/null @@ -1,19 +0,0 @@ -package util - -const ( - Bit0 Bitmask = 1 << iota - Bit1 - Bit2 - Bit3 - Bit4 - Bit5 - Bit6 - Bit7 -) - -type Bitmask uint8 - -func (b Bitmask) Has(flag Bitmask) bool { return b&flag != 0 } -func (b *Bitmask) Set(flag Bitmask) { *b |= flag } -func (b *Bitmask) Clear(flag Bitmask) { *b &= ^flag } -func (b *Bitmask) Toggle(flag Bitmask) { *b ^= flag } diff --git a/internal/pkg/routines/read_actions.go b/internal/pkg/routines/read_actions.go deleted file mode 100644 index 9ecf1e4..0000000 --- a/internal/pkg/routines/read_actions.go +++ /dev/null @@ -1,45 +0,0 @@ -package routines - -import ( - "fmt" - "log/slog" -) - -func ActionShowBasicAdminInfo(args I2cActionArgs) error { - slog.Info("module_info", slog.String("vendor_name", args.Page00.VendorName)) - slog.Info("module_info", slog.String("vendor_phy", args.Page00.VendorPN)) - slog.Info("module_info", slog.String("vendor_serial", args.Page00.VendorSN)) - slog.Info("module_info", slog.Bool("low_pwr_mode_enabled", args.Page00.LowPowerMode)) - - return nil -} - -func ActionShowTunableLaserStatus(args I2cActionArgs) error { - slog.Info("module_info", slog.String( - "tuning_status", fmt.Sprintf("%b", args.Page12.Status), - )) - slog.Info("module_info", slog.String("grid_spacing", args.Page12.GridDisplay)) - slog.Info("module_info", slog.Float64("frequency", float64(args.Page12.Frequency)*1e-12)) - slog.Info("module_info", slog.Int("frequency_offset", args.Page12.FrequencyOffset)) - if args.Page12.Channel != nil { - slog.Info("module_info", slog.Int("channel", *args.Page12.Channel)) - } else { - slog.Warn("No Valid Channel found!") - } - - return nil -} - -func ActionShowFlexOptixCustomPages(args I2cActionArgs) error { - // TODO: check with args.page00.VendorName - slog.Info("module_info", slog.Bool("flex_tune_enabled", args.Page1E.FlexTuneEnabled)) - slog.Info("module_info", slog.String("power_class_override_status", - fmt.Sprintf("%x", args.Page1E.PowerClassOverride), - )) - slog.Info("module_info", slog.Bool( - "nominal_wavelength_control_enabled", - args.Page1B.NominalWavelengthControlEnabled, - )) - - return nil -} diff --git a/internal/pkg/routines/routines.go b/internal/pkg/routines/routines.go deleted file mode 100644 index 301348b..0000000 --- a/internal/pkg/routines/routines.go +++ /dev/null @@ -1,102 +0,0 @@ -package routines - -import ( - "context" - - "github.com/urfave/cli/v3" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick/ssh" -) - -var CmdStringToPowerMode = map[string]optic.PowerMode{ - "high": optic.PowerModeHighPower, - "low": optic.PowerModeLowPower, -} - -type I2cActionArgs struct { - Handle *connection.I2cRWHandle - Cmd *cli.Command - Page00 *optic.I2CPage00 - Page12 *optic.I2CPage12 - Page1E *optic.I2CPage1E - Page1B *optic.I2CPageB0 -} - -type I2cAction func(args I2cActionArgs) error - -func I2cTemplateMethod(actions []I2cAction) cli.ActionFunc { - return func(_ context.Context, cmd *cli.Command) error { - user := cmd.String("user") - router := cmd.StringArg("device") - iface := cmd.StringArg("interface") - - handle, err := connection.NewI2cRWHandle(user, router, iface) - if err != nil { - return err - } - defer connection.CloseI2CRWHandle(handle) - - page00, err := handle.Connection.GetI2CDump(handle.I2cBusId) - if err != nil { - return err - } - page12, err := handle.Connection.GetI2CDump(handle.I2cBusId) - if err != nil { - return err - } - page1E, err := handle.Connection.GetI2CDump(handle.I2cBusId) - if err != nil { - return err - } - page1B, err := handle.Connection.GetI2CDump(handle.I2cBusId) - if err != nil { - return err - } - - resultPage00 := optic.InterpretPage00(pkg.ParseI2CDump(*page00)) - resultPage12 := optic.InterpretPage12(pkg.ParseI2CDump(*page12)) - resultPage1E := optic.InterpretPage1E(pkg.ParseI2CDump(*page1E)) - resultPage1B := optic.InterpretPageB0(pkg.ParseI2CDump(*page1B)) - - actionArgs := I2cActionArgs{ - Handle: handle, - Cmd: cmd, - Page00: &resultPage00, - Page12: &resultPage12, - Page1E: &resultPage1E, - Page1B: &resultPage1B, - } - - for _, action := range actions { - err := action(actionArgs) - if err != nil { - return err - } - } - - return nil - } -} - -var i2cReadActions = [...]I2cAction{ - ActionShowBasicAdminInfo, - ActionShowTunableLaserStatus, - ActionShowFlexOptixCustomPages, -} - -var I2CReadAll = I2cTemplateMethod(i2cReadActions[:]) - -var i2cWriteActions = [...]I2cAction{ - ActionShowBasicAdminInfo, - // some optics require disabling high power before programming - // ActionSetPowerModeTo(optic.PowerModeLowPower), - ActionShowFlexOptixCustomPages, // custom - // ActionDisableFlexTune, // custom - // ActionSetGridProgramming, // tunable laser - // ActionEnableNominalWavelengthControl, // custom - // ActionSetPowerClassOverride, // custom - // ActionSetPowerMode, -} - -var I2CWriteAll = I2cTemplateMethod(i2cWriteActions[:]) From 56f72a1d1e1c9a1e26f7a46880c5de369b36e37b Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 24 Jun 2026 17:59:03 +0200 Subject: [PATCH 26/36] move things around --- internal/pkg/optic/cmis/cmis.go | 475 +----------------------- internal/pkg/optic/cmis/common.go | 193 ++++++++++ internal/pkg/optic/cmis/constants.go | 141 +++++++ internal/pkg/optic/cmis/extension.go | 166 +++++++++ internal/pkg/optic/sff8636/common.go | 35 ++ internal/pkg/optic/sff8636/constants.go | 22 ++ internal/pkg/optic/sff8636/extension.go | 39 ++ internal/pkg/optic/sff8636/sff8636.go | 96 +---- internal/pkg/public.go | 102 ++--- 9 files changed, 652 insertions(+), 617 deletions(-) create mode 100644 internal/pkg/optic/cmis/common.go create mode 100644 internal/pkg/optic/cmis/constants.go create mode 100644 internal/pkg/optic/cmis/extension.go create mode 100644 internal/pkg/optic/sff8636/common.go create mode 100644 internal/pkg/optic/sff8636/constants.go create mode 100644 internal/pkg/optic/sff8636/extension.go diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index fe184e6..288b33a 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -2,315 +2,21 @@ package cmis import ( "fmt" - "slices" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" ) -const ( - BankSelectRegisterAddress = 0x7E - PageSelectRegisterAddress = 0x7F -) - -const ( - GridSupported75GhzMask = 0b1000_0000 >> iota - GridSupported33GhzMask - GridSupported100GhzMask - GridSupported50GhzMask - GridSupported25GhzMask - GridSupported12p5GhzMask - GridSupported6p25GhzMask - GridSupported3p125GhzMask -) -const ( - FineTuningSupportedMask = 0b1000_0000 >> iota - GridSupported150GhzMask -) - -const ( - ProgOutputPowerPerLaneSupported = 0b1000_0000 - MemoryModelMask = 0b1000_0000 - SteppedConfigOnlyMask = 0b0100_0000 - I2CMciMaxSpeedMask = 0b0011_0000 - SPIMCIMaxSpeedMask = 0b0000_1100 - AutoCommissioningMask = 0b0000_0011 - ModulePowerClassMask = 0b1110_0000 - FarEndConfigurationMask = 0b0001_0000 - BanksSupportedMask = 0b0000_0011 - ExtraLaneBanksSupportedMask = 0b0001_1111 -) - -const ( - WavelengthIsControllableMask = 0b1000_0000 - TransmitterIsTunableMask = 0b0100_0000 - SquelchMethodTxMask = 0b0011_0000 - ForcedSquelchTxSupportedMask = 0b0000_1000 - AutoSquelchDisableTxSupportedMask = 0b0000_0100 - OutputDisableTxSupportedMask = 0b0000_0010 - InputPolarityFlipTxSupportedMask = 0b0000_0001 - BankBroadcastSupportedMask = 0b1000_0000 - AutoSquelchDisableRxSupportedMask = 0b0000_0100 - OutputDisableRxSupportedMask = 0b0000_0010 - OutputPolarityFlipRxSupportedMask = 0b0000_0001 -) - -const TunableLaserControlStatusPage = 0x12 -const MaximumLaneNumber = 7 - -var I2CMciMaxSpeedToKhz = map[byte]int{ - 0x00: 400, - 0x10: 1_000, - 0x20: 3_400, - 0x30: 0, // reserved onwards - 0x40: 0, - 0x50: 0, - 0x60: 0, - 0x70: 0, - 0x80: 0, - 0x90: 0, - 0xA0: 0, - 0xB0: 0, - 0xC0: 0, - 0xD0: 0, - 0xE0: 0, - 0xF0: 0, -} - -var SPIMciMaxSpeedToKhz = map[byte]int{ - 0x00: 1_000, - 0x01: 2_000, - 0x02: 4_000, - 0x03: 8_000, - 0x04: 12_000, - 0x05: 16_000, - 0x06: 20_000, - 0x07: 30_000, - 0x08: 40_000, - 0x09: 50_000, - 0x0A: 0, // reserved onwards - 0x0B: 0, - 0x0C: 0, - 0x0D: 0, - 0x0E: 0, - 0x0F: 0, -} - -var PowerClassToInt = map[byte]int{ - 0b0000_0000: 1, - 0b0010_0000: 2, - 0b0100_0000: 3, - 0b0110_0000: 4, - 0b1000_0000: 5, - 0b1010_0000: 6, - 0b1100_0000: 7, - 0b1110_0000: 8, -} - -var MediaInterfaceToStr = map[byte]string{ - 0x00: "850nm VCSEL", - 0x01: "1310nm VCSEL", - 0x02: "1550nm VCSEL", - 0x03: "1310nm FP", - 0x04: "1310nm DFB", - 0x05: "1550nm DFB", - 0x06: "1310nm EML", - 0x07: "1550nm EML", - 0x08: "Other", - 0x09: "1490nm DFB", - 0x0A: "Copper cable, passive, unequalized", - 0x0B: "Copper cable, passive, equalized", - 0x0C: "Copper cable with near and far end limiting active equalizers", - 0x0D: "Copper cable with end limiting active equalizers", - 0x0E: "Copper cable with near end limiting active equalizers", - 0x0F: "Copper cable with linear active equalizers", - 0x10: "C-band tunable laser", - 0x11: "L-band tunable laser", - 0x12: "Copper cable with near and far end linear active equalizers", - 0x13: "Copper cable with far end linear active equalizers", - 0x14: "Copper cable with near end linear active equalizers", -} - -const BankSelectErrorString = "I could not write to bank select register, aborting program now." -const PageSelectErrorString = "I could not write to page select register, aborting program now." -const RegisterWriteErrorString = "I could not write to arbitrary register, aborting program now." -const PageOrBankNotAvailableTemplateErrorString = "Module responded that page 0x%x with bank 0x%x is not available," + - " its likely I failed to correctly identify the module capabilities. Aborting program now." -const NoStagedConfigSupportErrorString = "Module has signaled that staged config change is mandatory for any change, " + - "but I do not have this capability. Aborting program now." -const FlatMemoryMapErrorString = "Module signals a flat memory map. Only lower memory and page 00 are available, " + - "and you are requesting pages above this. Aborting program now." - type ManagementStrategy struct { state *pkg.ModuleState } -type ExtensionManagementStrategy struct { - state *pkg.ModuleState -} - func New(state *pkg.ModuleState) *ManagementStrategy { return &ManagementStrategy{ state: state, } } -func NewCMISExtension(state *pkg.ModuleState) *ExtensionManagementStrategy { - return &ExtensionManagementStrategy{ - state: state, - } -} - -func (e *ExtensionManagementStrategy) assumeHigherPagesReadable() { - if !e.state.CMIS.MemoryModelPaged { - panic(FlatMemoryMapErrorString) - } -} - -// assumeConfigChange ensures that module is placed in a config change state prior to write attempt. -// Requires that administrative information has already been obtained prior to write attempt -func (e *ExtensionManagementStrategy) assumeConfigChange() { - e.assumeHigherPagesReadable() - if e.state.CMIS.SteppedConfigOnly { - panic(NoStagedConfigSupportErrorString) - } -} - -func (e *ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { - _, err := e.GetSupportedControlsAdvertising() - if err != nil { - return nil, err - } - _, err = e.GetLaserCapabilitiesAdvertising() - if err != nil { - return nil, err - } - _, err = e.GetTunableLaserControlStatus() - if err != nil { - return nil, err - } - - return e.state, nil -} - -func (e *ExtensionManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { - e.assumeConfigChange() - - _, err := e.SetTunableLaserControlStatus(e.state) - if err != nil { - return nil, err - } - - return e.state, nil -} - -func (e *ExtensionManagementStrategy) ManufacturerIsCompatibleWithProtocolExtension(manufacturer string) bool { - return true // manufacturer names have 0 influence, only sff8024 does. so we defer decision -} - -func (e *ExtensionManagementStrategy) SFF8024IsCompatibleWithProtocolExtension( - sff8024Identifier byte, - sff8024Revision byte, -) bool { - return checkSFF8024(sff8024Identifier, sff8024Revision) -} - -func (e *ExtensionManagementStrategy) Activate() (*pkg.ModuleState, error) { - e.state.CMIS.Active = true - return e.state, nil -} - -func (e *ExtensionManagementStrategy) GetTunableLaserControlStatus() (*pkg.ModuleState, error) { - e.assumeHigherPagesReadable() - - var bank byte - e.state.CMIS.TunableLaser.CtrlStatus = nil - for bank = 0x00; bank <= e.state.CMIS.SupportedControls.MaximumBankSupported; bank += 1 { - e.state.CMIS.TunableLaser.CtrlStatus = append( - e.state.CMIS.TunableLaser.CtrlStatus, - pkg.CMISBankedTunableLaserControlAndStatus{}, - ) // adding banks on the go to avoid having max banks all the time - _, err := GetTunableLaserControlStatus( - e.state, &e.state.CMIS.TunableLaser.CtrlStatus[bank], bank, MaximumLaneNumber, - ) - if err != nil { - return nil, err - } - } - - return e.state, nil -} - -func (e *ExtensionManagementStrategy) SetTunableLaserControlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { - e.assumeHigherPagesReadable() - - for i, bank := range e.state.CMIS.TunableLaser.CtrlStatus { - _, err := SetTunableLaserControlStatus(e.state, &bank, byte(i), MaximumLaneNumber) - if err != nil { - return nil, err - } - } - - return e.state, nil -} - -func (e *ExtensionManagementStrategy) GetLaserCapabilitiesAdvertising() (*pkg.ModuleState, error) { - e.assumeHigherPagesReadable() - - _, err := GetLaserCapabilitiesAdvertising( - e.state, &e.state.CMIS.TunableLaser.Capabilities, - ) - if err != nil { - return nil, err - } - - return e.state, nil -} - -func (e *ExtensionManagementStrategy) GetSupportedControlsAdvertising() (*pkg.ModuleState, error) { - e.assumeHigherPagesReadable() - - dumpBin, err := e.state.GetPageBin(0x01, 0x00) - if err != nil { - return nil, err - } - - caps := &e.state.CMIS.SupportedControls - - caps.ModuleInactiveFirmwareMajorRevision = dumpBin[0x80] - caps.ModuleInactiveFirmwareMinorRevision = dumpBin[0x81] - caps.ModuleHardwareMajorRevision = dumpBin[0x82] - caps.ModuleHardwareMinorRevision = dumpBin[0x83] - - // skipping supported lengths - - // only defined for SINGLE WAVELENGTH modules - var base byte = 0x8A - caps.NominalWavelengthNm = 0.05 * float64(util.ReadBeUint16AndShiftBase(dumpBin, &base)) - caps.WavelengthToleranceNm = 0.005 * float64(util.ReadBeUint16AndShiftBase(dumpBin, &base)) - - // skipping network / vdm / diag / coherent / cmisff / 03h - caps.MaximumBankSupported = dumpBin[0x8E] & BanksSupportedMask - if caps.MaximumBankSupported == 0x03 { // signals lane banked - caps.MaximumBankSupported = dumpBin[0xAE] & ExtraLaneBanksSupportedMask - } - - // go should have bitfields - caps.WavelengthIsControllable = dumpBin[0x9B]&WavelengthIsControllableMask != 0 - caps.TransmitterIsTunable = dumpBin[0x9B]&TransmitterIsTunableMask != 0 - caps.SquelchMethodTx = dumpBin[0x9B] & SquelchMethodTxMask - caps.ForcedSquelchTxSupported = dumpBin[0x9B]&ForcedSquelchTxSupportedMask != 0 - caps.AutoSquelchDisableTxSupported = dumpBin[0x9B]&AutoSquelchDisableTxSupportedMask != 0 - caps.OutputDisableTxSupported = dumpBin[0x9B]&OutputDisableTxSupportedMask != 0 - caps.InputPolarityFlipTxSupported = dumpBin[0x9B]&InputPolarityFlipTxSupportedMask != 0 - caps.BankBroadcastSupported = dumpBin[0x9C]&BankBroadcastSupportedMask != 0 - caps.AutoSquelchDisableRxSupported = dumpBin[0x9C]&AutoSquelchDisableRxSupportedMask != 0 - caps.OutputDisableRxSupported = dumpBin[0x9C]&OutputDisableRxSupportedMask != 0 - caps.OutputPolarityFlipRxSupported = dumpBin[0x9C]&OutputPolarityFlipRxSupportedMask != 0 - - return e.state, nil -} - func (s2 *ManagementStrategy) GetPageBin(page byte, bank byte) ([]byte, error) { handle := s2.state.GetHandle() @@ -368,47 +74,12 @@ func (s2 *ManagementStrategy) WritePageByteBin(page byte, bank byte, offset byte return nil } -const RefusalMajorTooHigh string = "This CMIS module has" + - " a Major Revision number over what I can speak " + - "therefore it is unsupported. Program will be terminated " + - "now, as I cannot read nor write to this module without " + - "potential failure, data loss and/or equipment damage." - -func checkSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { - var CmisCompatibleSFF8024IDs = [...]byte{ - 0x1E, // qsfp+ or later with cmis - 0x1F, // sfp-dd with cmis - 0x20, // sfp+ or later with cmis - 0x21, // osfp-xd with cmis - 0x22, // oif-elfs with cmis - 0x23, // 4 lanes cdfp with cmis - 0x24, // 8 lanes cdfp with cmis - 0x25, // 16 lanes cdfp with cmis - 0x18, // qsfp-dd 8x - may support cmis - } - - const MajVerMask byte = 0xF0 // upper nibble is bits 7-4 per cmis rev 5.38 section 8.2.1 - const MinMajVer byte = 0x50 // minimum supported major version is 5 - - compatibleIdentifier := func(id byte) bool { return slices.Contains(CmisCompatibleSFF8024IDs[:], id) } - compatibleMajorRev := func(sff8024rev byte) bool { return (sff8024rev & MajVerMask) <= MinMajVer } - - // panic if CMIS and revision is above our maximum - if compatibleIdentifier(sff8024Identifier) && !compatibleMajorRev(sff8024Revision) { - panic(RefusalMajorTooHigh) - } - - // tells if compatible - return compatibleIdentifier(sff8024Identifier) -} - func (s2 *ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { return checkSFF8024(sff8024Identifier, sff8024Revision) } func (s2 *ManagementStrategy) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { - //TODO implement me - panic("implement me") + return s2.state, nil // noop } func (s2 *ManagementStrategy) Get() (*pkg.ModuleState, error) { @@ -470,147 +141,3 @@ func (s2 *ManagementStrategy) SetAdministrativeInformation(_ *pkg.ModuleState) ( // noop return s2.state, nil } - -// GetTunableLaserControlStatus maxN between 0 and 7 (channel N, used for only-channel-0 access) -func GetTunableLaserControlStatus(state *pkg.ModuleState, caps *pkg.CMISBankedTunableLaserControlAndStatus, bank byte, maxN int) (*pkg.ModuleState, error) { - dumpBin, err := state.GetPageBin(TunableLaserControlStatusPage, bank) - if err != nil { - return nil, err - } - - for i := 0; i <= maxN; i += 1 { - caps.GridSpacingTx[i] = dumpBin[0x80+i] & pkg.CMISGridSpacingTxMask - caps.GridSpacingTxROGhz[i] = pkg.CMISGridSpacingToFloatGhzMap[caps.GridSpacingTx[i]] - caps.FineTuningEnableTx[i] = dumpBin[0x80+i]&pkg.CMISFineTuningEnableTxMask != 0 - - caps.ChannelNumberTx[i] = util.ReadBeInt16(dumpBin, 0x88+byte(2*i)) // S16 over 2 bytes - caps.FineTuningOffsetMhzTx[i] = util.ReadBeInt16(dumpBin, 0x98+byte(2*i)) - caps.CurrentLaserFrequencyMhzTx[i] = util.ReadBeUint32(dumpBin, 0xA8+byte(4*i)) // U32 over 4 bytes, units Mhz - - caps.TargetOutputPowerTx[i] = util.ReadBeInt16(dumpBin, 0xC8+byte(2*i)) - - caps.TuningInProgressTx[i] = dumpBin[0xDE+i]&pkg.CMISTuningInProgressTxMask != 0 - caps.WaveLengthUnlockStatus[i] = dumpBin[0xDE+i]&pkg.CMISWavelengthUnlockStatusTxMask != 0 - - // per spec: the bit n-1 is set if and only if any of the latched flags are set to 1 - caps.LaserTuningFlagSummaryTx[i] = (dumpBin[0xE6+i] & (0b0000_0001 << i)) != 0 - - caps.TargetOutputPowerOORFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISTargetOutputPowerOORFlagTxMask != 0 - caps.FineTuningOutOfRangeFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISFineTuningOutOfRangeFlagTxMask != 0 - caps.TuningNotAcceptedMaskTx[i] = dumpBin[0xE7+i]&pkg.CMISTuningNotAcceptedFlagTxMask != 0 - caps.InvalidChannelNumberFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISInvalidChannelNumberFlagTxMask != 0 - caps.WavelengthUnlockedFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISWavelengthUnlockedFlagTxMask != 0 - caps.TuningCompleteFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISTuningCompleteFlagTxMask != 0 - - // reading interrupt masks, bitfield pattern same as alarm - caps.TargetOutputPowerOORMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISTargetOutputPowerOORFlagTxMask != 0 - caps.FineTuningPowerOutOfRangeMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISFineTuningOutOfRangeFlagTxMask != 0 - caps.TuningNotAcceptedMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISTuningNotAcceptedFlagTxMask != 0 - caps.InvalidChannelMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISInvalidChannelNumberFlagTxMask != 0 - caps.WavelengthUnlockedMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISWavelengthUnlockedFlagTxMask != 0 - caps.TuningCompleteMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISTuningCompleteFlagTxMask != 0 - } - - return state, nil -} - -func SetTunableLaserControlStatus(state *pkg.ModuleState, caps *pkg.CMISBankedTunableLaserControlAndStatus, bank byte, maxN int) (*pkg.ModuleState, error) { - dumpBin, err := state.GetPageBin(TunableLaserControlStatusPage, bank) - if err != nil { - return nil, err - } - - for i := 0; i <= maxN; i += 1 { - // rewrite to mem - dumpBin[0x80+i] = (caps.GridSpacingTx[i] & pkg.CMISGridSpacingTxMask) & - (util.YesNoByte(caps.FineTuningEnableTx[i]) & pkg.CMISFineTuningEnableTxMask) - - util.WriteBeInt16(caps.ChannelNumberTx[i], dumpBin, 0x88+byte(2*i)) - util.WriteBeInt16(caps.FineTuningOffsetMhzTx[i], dumpBin, 0x98+byte(2*i)) - // no write for CurrentLaserFrequency, read-only - - util.WriteBeInt16(caps.TargetOutputPowerTx[i], dumpBin, 0xC8+byte(2*i)) - // subsequent fields read-only - - dumpBin[0xEF+i] = (util.YesNoByte(caps.TargetOutputPowerOORMaskTx[i]) & pkg.CMISTargetOutputPowerOORFlagTxMask) & - (util.YesNoByte(caps.FineTuningPowerOutOfRangeMaskTx[i]) & pkg.CMISFineTuningOutOfRangeFlagTxMask) & - (util.YesNoByte(caps.TuningNotAcceptedMaskTx[i]) & pkg.CMISTuningNotAcceptedFlagTxMask) & - (util.YesNoByte(caps.InvalidChannelMaskTx[i]) & pkg.CMISInvalidChannelNumberFlagTxMask) & - (util.YesNoByte(caps.WavelengthUnlockedMaskTx[i]) & pkg.CMISWavelengthUnlockedFlagTxMask) & - (util.YesNoByte(caps.TuningCompleteMaskTx[i]) & pkg.CMISTuningCompleteFlagTxMask) - } - - err = state.WritePageBin(TunableLaserControlStatusPage, bank, dumpBin) - if err != nil { - return nil, err - } - - return state, nil -} - -func GetLaserCapabilitiesAdvertising(state *pkg.ModuleState, caps *pkg.CMISLaserCapabilitiesAdvertising) (*pkg.ModuleState, error) { - dumpBin, err := state.GetPageBin(0x04, 0x00) // non-banked - if err != nil { - return nil, err - } - - caps.SupportedGridSpacings = make(map[string]bool) - - // I really hate that Go doesn't have bitfields. - caps.SupportedGridSpacings[pkg.Grid75string] = dumpBin[0x80]&GridSupported75GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid33String] = dumpBin[0x80]&GridSupported33GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid100String] = dumpBin[0x80]&GridSupported100GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid50String] = dumpBin[0x80]&GridSupported50GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid25String] = dumpBin[0x80]&GridSupported25GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid12p5String] = dumpBin[0x80]&GridSupported12p5GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid6p250String] = dumpBin[0x80]&GridSupported6p25GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid3p125String] = dumpBin[0x80]&GridSupported3p125GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid150String] = dumpBin[0x81]&GridSupported150GhzMask != 0 - - caps.FineTuningSupported = dumpBin[0x81]&FineTuningSupportedMask != 0 - - caps.GridLowChannel = make(map[string]int16) - caps.GridHighChannel = make(map[string]int16) - - var base byte = 0x82 - // 3.125Ghz - caps.GridLowChannel[pkg.Grid3p125String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid3p125String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 6.25Ghz - caps.GridLowChannel[pkg.Grid6p250String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid6p250String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 12.5Ghz - caps.GridLowChannel[pkg.Grid12p5String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid12p5String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 25Ghz - caps.GridLowChannel[pkg.Grid25String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid25String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 50Ghz - caps.GridLowChannel[pkg.Grid50String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid50String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 100Ghz - caps.GridLowChannel[pkg.Grid100String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid100String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 33Ghz - caps.GridLowChannel[pkg.Grid33String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid33String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 75Ghz - caps.GridLowChannel[pkg.Grid75string] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid75string] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 150Ghz - caps.GridLowChannel[pkg.Grid150String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid150String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - - base = 0xBE // skip reserved region - caps.FineTuningResolution = util.ReadBeUint16AndShiftBase(dumpBin, &base) - caps.FineTuningLowOffset = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.FineTuningHighOffset = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.ProgOutputPowerPerLaneSupported = dumpBin[base]&ProgOutputPowerPerLaneSupported != 0 - - base = 0xC6 // skip reserved region - caps.ProgOutputPowerMin = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.ProgOutputPowerMax = util.ReadBeInt16AndShiftBase(dumpBin, &base) - - // TODO checksum support - return state, nil -} diff --git a/internal/pkg/optic/cmis/common.go b/internal/pkg/optic/cmis/common.go new file mode 100644 index 0000000..600e6ed --- /dev/null +++ b/internal/pkg/optic/cmis/common.go @@ -0,0 +1,193 @@ +package cmis + +import ( + "slices" + + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" +) + +func checkSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { + var CmisCompatibleSFF8024IDs = [...]byte{ + 0x1E, // qsfp+ or later with cmis + 0x1F, // sfp-dd with cmis + 0x20, // sfp+ or later with cmis + 0x21, // osfp-xd with cmis + 0x22, // oif-elfs with cmis + 0x23, // 4 lanes cdfp with cmis + 0x24, // 8 lanes cdfp with cmis + 0x25, // 16 lanes cdfp with cmis + 0x18, // qsfp-dd 8x - may support cmis + } + + const MajVerMask byte = 0xF0 // upper nibble is bits 7-4 per cmis rev 5.38 section 8.2.1 + const MinMajVer byte = 0x50 // minimum supported major version is 5 + + compatibleIdentifier := func(id byte) bool { return slices.Contains(CmisCompatibleSFF8024IDs[:], id) } + compatibleMajorRev := func(sff8024rev byte) bool { return (sff8024rev & MajVerMask) <= MinMajVer } + + // panic if CMIS and revision is above our maximum + if compatibleIdentifier(sff8024Identifier) && !compatibleMajorRev(sff8024Revision) { + panic(RefusalMajorTooHigh) + } + + // tells if compatible + return compatibleIdentifier(sff8024Identifier) +} + +// GetTunableLaserControlStatus maxN between 0 and 7 (channel N, used for only-channel-0 access) +func GetTunableLaserControlStatus( + state *pkg.ModuleState, + caps *pkg.CMISBankedTunableLaserControlAndStatus, + bank byte, + maxN int, +) (*pkg.ModuleState, error) { + dumpBin, err := state.GetPageBin(TunableLaserControlStatusPage, bank) + if err != nil { + return nil, err + } + + for i := 0; i <= maxN; i += 1 { + caps.GridSpacingTx[i] = dumpBin[0x80+i] & pkg.CMISGridSpacingTxMask + caps.GridSpacingTxROGhz[i] = pkg.CMISGridSpacingToFloatGhzMap[caps.GridSpacingTx[i]] + caps.FineTuningEnableTx[i] = dumpBin[0x80+i]&pkg.CMISFineTuningEnableTxMask != 0 + + caps.ChannelNumberTx[i] = util.ReadBeInt16(dumpBin, 0x88+byte(2*i)) // S16 over 2 bytes + caps.FineTuningOffsetMhzTx[i] = util.ReadBeInt16(dumpBin, 0x98+byte(2*i)) + caps.CurrentLaserFrequencyMhzTx[i] = util.ReadBeUint32(dumpBin, 0xA8+byte(4*i)) // U32 over 4 bytes, units Mhz + + caps.TargetOutputPowerTx[i] = util.ReadBeInt16(dumpBin, 0xC8+byte(2*i)) + + caps.TuningInProgressTx[i] = dumpBin[0xDE+i]&pkg.CMISTuningInProgressTxMask != 0 + caps.WaveLengthUnlockStatus[i] = dumpBin[0xDE+i]&pkg.CMISWavelengthUnlockStatusTxMask != 0 + + // per spec: the bit n-1 is set if and only if any of the latched flags are set to 1 + caps.LaserTuningFlagSummaryTx[i] = (dumpBin[0xE6+i] & (0b0000_0001 << i)) != 0 + + caps.TargetOutputPowerOORFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISTargetOutputPowerOORFlagTxMask != 0 + caps.FineTuningOutOfRangeFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISFineTuningOutOfRangeFlagTxMask != 0 + caps.TuningNotAcceptedMaskTx[i] = dumpBin[0xE7+i]&pkg.CMISTuningNotAcceptedFlagTxMask != 0 + caps.InvalidChannelNumberFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISInvalidChannelNumberFlagTxMask != 0 + caps.WavelengthUnlockedFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISWavelengthUnlockedFlagTxMask != 0 + caps.TuningCompleteFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISTuningCompleteFlagTxMask != 0 + + // reading interrupt masks, bitfield pattern same as alarm + caps.TargetOutputPowerOORMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISTargetOutputPowerOORFlagTxMask != 0 + caps.FineTuningPowerOutOfRangeMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISFineTuningOutOfRangeFlagTxMask != 0 + caps.TuningNotAcceptedMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISTuningNotAcceptedFlagTxMask != 0 + caps.InvalidChannelMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISInvalidChannelNumberFlagTxMask != 0 + caps.WavelengthUnlockedMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISWavelengthUnlockedFlagTxMask != 0 + caps.TuningCompleteMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISTuningCompleteFlagTxMask != 0 + } + + return state, nil +} + +func SetTunableLaserControlStatus( + state *pkg.ModuleState, + caps *pkg.CMISBankedTunableLaserControlAndStatus, + bank byte, + maxN int, +) (*pkg.ModuleState, error) { + dumpBin, err := state.GetPageBin(TunableLaserControlStatusPage, bank) + if err != nil { + return nil, err + } + + for i := 0; i <= maxN; i += 1 { + // rewrite to mem + dumpBin[0x80+i] = (caps.GridSpacingTx[i] & pkg.CMISGridSpacingTxMask) & + (util.YesNoByte(caps.FineTuningEnableTx[i]) & pkg.CMISFineTuningEnableTxMask) + + util.WriteBeInt16(caps.ChannelNumberTx[i], dumpBin, 0x88+byte(2*i)) + util.WriteBeInt16(caps.FineTuningOffsetMhzTx[i], dumpBin, 0x98+byte(2*i)) + // no write for CurrentLaserFrequency, read-only + + util.WriteBeInt16(caps.TargetOutputPowerTx[i], dumpBin, 0xC8+byte(2*i)) + // subsequent fields read-only + + dumpBin[0xEF+i] = (util.YesNoByte(caps.TargetOutputPowerOORMaskTx[i]) & pkg.CMISTargetOutputPowerOORFlagTxMask) & + (util.YesNoByte(caps.FineTuningPowerOutOfRangeMaskTx[i]) & pkg.CMISFineTuningOutOfRangeFlagTxMask) & + (util.YesNoByte(caps.TuningNotAcceptedMaskTx[i]) & pkg.CMISTuningNotAcceptedFlagTxMask) & + (util.YesNoByte(caps.InvalidChannelMaskTx[i]) & pkg.CMISInvalidChannelNumberFlagTxMask) & + (util.YesNoByte(caps.WavelengthUnlockedMaskTx[i]) & pkg.CMISWavelengthUnlockedFlagTxMask) & + (util.YesNoByte(caps.TuningCompleteMaskTx[i]) & pkg.CMISTuningCompleteFlagTxMask) + } + + err = state.WritePageBin(TunableLaserControlStatusPage, bank, dumpBin) + if err != nil { + return nil, err + } + + return state, nil +} + +func GetLaserCapabilitiesAdvertising( + state *pkg.ModuleState, + caps *pkg.CMISLaserCapabilitiesAdvertising, +) (*pkg.ModuleState, error) { + dumpBin, err := state.GetPageBin(0x04, 0x00) // non-banked + if err != nil { + return nil, err + } + + caps.SupportedGridSpacings = make(map[string]bool) + + // I really hate that Go doesn't have bitfields. + caps.SupportedGridSpacings[pkg.Grid75string] = dumpBin[0x80]&GridSupported75GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid33String] = dumpBin[0x80]&GridSupported33GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid100String] = dumpBin[0x80]&GridSupported100GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid50String] = dumpBin[0x80]&GridSupported50GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid25String] = dumpBin[0x80]&GridSupported25GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid12p5String] = dumpBin[0x80]&GridSupported12p5GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid6p250String] = dumpBin[0x80]&GridSupported6p25GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid3p125String] = dumpBin[0x80]&GridSupported3p125GhzMask != 0 + caps.SupportedGridSpacings[pkg.Grid150String] = dumpBin[0x81]&GridSupported150GhzMask != 0 + + caps.FineTuningSupported = dumpBin[0x81]&FineTuningSupportedMask != 0 + + caps.GridLowChannel = make(map[string]int16) + caps.GridHighChannel = make(map[string]int16) + + var base byte = 0x82 + // 3.125Ghz + caps.GridLowChannel[pkg.Grid3p125String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid3p125String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 6.25Ghz + caps.GridLowChannel[pkg.Grid6p250String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid6p250String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 12.5Ghz + caps.GridLowChannel[pkg.Grid12p5String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid12p5String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 25Ghz + caps.GridLowChannel[pkg.Grid25String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid25String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 50Ghz + caps.GridLowChannel[pkg.Grid50String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid50String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 100Ghz + caps.GridLowChannel[pkg.Grid100String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid100String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 33Ghz + caps.GridLowChannel[pkg.Grid33String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid33String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 75Ghz + caps.GridLowChannel[pkg.Grid75string] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid75string] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 150Ghz + caps.GridLowChannel[pkg.Grid150String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[pkg.Grid150String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + + base = 0xBE // skip reserved region + caps.FineTuningResolution = util.ReadBeUint16AndShiftBase(dumpBin, &base) + caps.FineTuningLowOffset = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.FineTuningHighOffset = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.ProgOutputPowerPerLaneSupported = dumpBin[base]&ProgOutputPowerPerLaneSupported != 0 + + base = 0xC6 // skip reserved region + caps.ProgOutputPowerMin = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.ProgOutputPowerMax = util.ReadBeInt16AndShiftBase(dumpBin, &base) + + // TODO checksum support + return state, nil +} diff --git a/internal/pkg/optic/cmis/constants.go b/internal/pkg/optic/cmis/constants.go new file mode 100644 index 0000000..3ef8a74 --- /dev/null +++ b/internal/pkg/optic/cmis/constants.go @@ -0,0 +1,141 @@ +package cmis + +const ( + BankSelectRegisterAddress = 0x7E + PageSelectRegisterAddress = 0x7F +) + +const ( + GridSupported75GhzMask = 0b1000_0000 >> iota + GridSupported33GhzMask + GridSupported100GhzMask + GridSupported50GhzMask + GridSupported25GhzMask + GridSupported12p5GhzMask + GridSupported6p25GhzMask + GridSupported3p125GhzMask +) +const ( + FineTuningSupportedMask = 0b1000_0000 >> iota + GridSupported150GhzMask +) + +const ( + ProgOutputPowerPerLaneSupported = 0b1000_0000 + MemoryModelMask = 0b1000_0000 + SteppedConfigOnlyMask = 0b0100_0000 + I2CMciMaxSpeedMask = 0b0011_0000 + SPIMCIMaxSpeedMask = 0b0000_1100 + AutoCommissioningMask = 0b0000_0011 + ModulePowerClassMask = 0b1110_0000 + FarEndConfigurationMask = 0b0001_0000 + BanksSupportedMask = 0b0000_0011 + ExtraLaneBanksSupportedMask = 0b0001_1111 +) + +const ( + WavelengthIsControllableMask = 0b1000_0000 + TransmitterIsTunableMask = 0b0100_0000 + SquelchMethodTxMask = 0b0011_0000 + ForcedSquelchTxSupportedMask = 0b0000_1000 + AutoSquelchDisableTxSupportedMask = 0b0000_0100 + OutputDisableTxSupportedMask = 0b0000_0010 + InputPolarityFlipTxSupportedMask = 0b0000_0001 + BankBroadcastSupportedMask = 0b1000_0000 + AutoSquelchDisableRxSupportedMask = 0b0000_0100 + OutputDisableRxSupportedMask = 0b0000_0010 + OutputPolarityFlipRxSupportedMask = 0b0000_0001 +) + +const TunableLaserControlStatusPage = 0x12 +const MaximumLaneNumber = 7 + +var I2CMciMaxSpeedToKhz = map[byte]int{ + 0x00: 400, + 0x10: 1_000, + 0x20: 3_400, + 0x30: 0, // reserved onwards + 0x40: 0, + 0x50: 0, + 0x60: 0, + 0x70: 0, + 0x80: 0, + 0x90: 0, + 0xA0: 0, + 0xB0: 0, + 0xC0: 0, + 0xD0: 0, + 0xE0: 0, + 0xF0: 0, +} + +var SPIMciMaxSpeedToKhz = map[byte]int{ + 0x00: 1_000, + 0x01: 2_000, + 0x02: 4_000, + 0x03: 8_000, + 0x04: 12_000, + 0x05: 16_000, + 0x06: 20_000, + 0x07: 30_000, + 0x08: 40_000, + 0x09: 50_000, + 0x0A: 0, // reserved onwards + 0x0B: 0, + 0x0C: 0, + 0x0D: 0, + 0x0E: 0, + 0x0F: 0, +} + +var PowerClassToInt = map[byte]int{ + 0b0000_0000: 1, + 0b0010_0000: 2, + 0b0100_0000: 3, + 0b0110_0000: 4, + 0b1000_0000: 5, + 0b1010_0000: 6, + 0b1100_0000: 7, + 0b1110_0000: 8, +} + +var MediaInterfaceToStr = map[byte]string{ + 0x00: "850nm VCSEL", + 0x01: "1310nm VCSEL", + 0x02: "1550nm VCSEL", + 0x03: "1310nm FP", + 0x04: "1310nm DFB", + 0x05: "1550nm DFB", + 0x06: "1310nm EML", + 0x07: "1550nm EML", + 0x08: "Other", + 0x09: "1490nm DFB", + 0x0A: "Copper cable, passive, unequalized", + 0x0B: "Copper cable, passive, equalized", + 0x0C: "Copper cable with near and far end limiting active equalizers", + 0x0D: "Copper cable with end limiting active equalizers", + 0x0E: "Copper cable with near end limiting active equalizers", + 0x0F: "Copper cable with linear active equalizers", + 0x10: "C-band tunable laser", + 0x11: "L-band tunable laser", + 0x12: "Copper cable with near and far end linear active equalizers", + 0x13: "Copper cable with far end linear active equalizers", + 0x14: "Copper cable with near end linear active equalizers", +} + +const ( + BankSelectErrorString = "I could not write to bank select register, aborting program now." + PageSelectErrorString = "I could not write to page select register, aborting program now." + RegisterWriteErrorString = "I could not write to arbitrary register, aborting program now." + PageOrBankNotAvailableTemplateErrorString = "Module responded that page 0x%x with bank 0x%x is not available," + + " its likely I failed to correctly identify the module capabilities. Aborting program now." + NoStagedConfigSupportErrorString = "Module has signaled that staged config change is mandatory for any change, " + + "but I do not have this capability. Aborting program now." + FlatMemoryMapErrorString = "Module signals a flat memory map. Only lower memory and page 00 are available, " + + "and you are requesting pages above this. Aborting program now." + RefusalMajorTooHigh string = "This CMIS module has" + + " a Major Revision number over what I can speak " + + "therefore it is unsupported. Program will be terminated " + + "now, as I cannot read nor write to this module without " + + "potential failure, data loss and/or equipment damage." +) diff --git a/internal/pkg/optic/cmis/extension.go b/internal/pkg/optic/cmis/extension.go new file mode 100644 index 0000000..5909c4e --- /dev/null +++ b/internal/pkg/optic/cmis/extension.go @@ -0,0 +1,166 @@ +package cmis + +import ( + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" + "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" +) + +type ExtensionManagementStrategy struct { + state *pkg.ModuleState +} + +func NewCMISExtension(state *pkg.ModuleState) *ExtensionManagementStrategy { + return &ExtensionManagementStrategy{ + state: state, + } +} + +func (e *ExtensionManagementStrategy) assumeHigherPagesReadable() { + if !e.state.CMIS.MemoryModelPaged { + panic(FlatMemoryMapErrorString) + } +} + +// assumeConfigChange ensures that module is placed in a config change state prior to write attempt. +// Requires that administrative information has already been obtained prior to write attempt +func (e *ExtensionManagementStrategy) assumeConfigChange() { + e.assumeHigherPagesReadable() + if e.state.CMIS.SteppedConfigOnly { + panic(NoStagedConfigSupportErrorString) + } +} + +func (e *ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { + _, err := e.GetSupportedControlsAdvertising() + if err != nil { + return nil, err + } + _, err = e.GetLaserCapabilitiesAdvertising() + if err != nil { + return nil, err + } + _, err = e.GetTunableLaserControlStatus() + if err != nil { + return nil, err + } + + return e.state, nil +} + +func (e *ExtensionManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { + e.assumeConfigChange() + + _, err := e.SetTunableLaserControlStatus(e.state) + if err != nil { + return nil, err + } + + return e.state, nil +} + +func (e *ExtensionManagementStrategy) ManufacturerIsCompatibleWithProtocolExtension(manufacturer string) bool { + return true // manufacturer names have 0 influence, only sff8024 does. so we defer decision +} + +func (e *ExtensionManagementStrategy) SFF8024IsCompatibleWithProtocolExtension( + sff8024Identifier byte, + sff8024Revision byte, +) bool { + return checkSFF8024(sff8024Identifier, sff8024Revision) +} + +func (e *ExtensionManagementStrategy) Activate() (*pkg.ModuleState, error) { + e.state.CMIS.Active = true + return e.state, nil +} + +func (e *ExtensionManagementStrategy) GetTunableLaserControlStatus() (*pkg.ModuleState, error) { + e.assumeHigherPagesReadable() + + var bank byte + e.state.CMIS.TunableLaser.CtrlStatus = nil + for bank = 0x00; bank <= e.state.CMIS.SupportedControls.MaximumBankSupported; bank += 1 { + e.state.CMIS.TunableLaser.CtrlStatus = append( + e.state.CMIS.TunableLaser.CtrlStatus, + pkg.CMISBankedTunableLaserControlAndStatus{}, + ) // adding banks on the go to avoid having max banks all the time + _, err := GetTunableLaserControlStatus( + e.state, &e.state.CMIS.TunableLaser.CtrlStatus[bank], bank, MaximumLaneNumber, + ) + if err != nil { + return nil, err + } + } + + return e.state, nil +} + +func (e *ExtensionManagementStrategy) SetTunableLaserControlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { + e.assumeHigherPagesReadable() + + for i, bank := range e.state.CMIS.TunableLaser.CtrlStatus { + _, err := SetTunableLaserControlStatus(e.state, &bank, byte(i), MaximumLaneNumber) + if err != nil { + return nil, err + } + } + + return e.state, nil +} + +func (e *ExtensionManagementStrategy) GetLaserCapabilitiesAdvertising() (*pkg.ModuleState, error) { + e.assumeHigherPagesReadable() + + _, err := GetLaserCapabilitiesAdvertising( + e.state, &e.state.CMIS.TunableLaser.Capabilities, + ) + if err != nil { + return nil, err + } + + return e.state, nil +} + +func (e *ExtensionManagementStrategy) GetSupportedControlsAdvertising() (*pkg.ModuleState, error) { + e.assumeHigherPagesReadable() + + dumpBin, err := e.state.GetPageBin(0x01, 0x00) + if err != nil { + return nil, err + } + + caps := &e.state.CMIS.SupportedControls + + caps.ModuleInactiveFirmwareMajorRevision = dumpBin[0x80] + caps.ModuleInactiveFirmwareMinorRevision = dumpBin[0x81] + caps.ModuleHardwareMajorRevision = dumpBin[0x82] + caps.ModuleHardwareMinorRevision = dumpBin[0x83] + + // skipping supported lengths + + // only defined for SINGLE WAVELENGTH modules + var base byte = 0x8A + caps.NominalWavelengthNm = 0.05 * float64(util.ReadBeUint16AndShiftBase(dumpBin, &base)) + caps.WavelengthToleranceNm = 0.005 * float64(util.ReadBeUint16AndShiftBase(dumpBin, &base)) + + // skipping network / vdm / diag / coherent / cmisff / 03h + caps.MaximumBankSupported = dumpBin[0x8E] & BanksSupportedMask + if caps.MaximumBankSupported == 0x03 { // signals lane banked + caps.MaximumBankSupported = dumpBin[0xAE] & ExtraLaneBanksSupportedMask + } + + // go should have bitfields + caps.WavelengthIsControllable = dumpBin[0x9B]&WavelengthIsControllableMask != 0 + caps.TransmitterIsTunable = dumpBin[0x9B]&TransmitterIsTunableMask != 0 + caps.SquelchMethodTx = dumpBin[0x9B] & SquelchMethodTxMask + caps.ForcedSquelchTxSupported = dumpBin[0x9B]&ForcedSquelchTxSupportedMask != 0 + caps.AutoSquelchDisableTxSupported = dumpBin[0x9B]&AutoSquelchDisableTxSupportedMask != 0 + caps.OutputDisableTxSupported = dumpBin[0x9B]&OutputDisableTxSupportedMask != 0 + caps.InputPolarityFlipTxSupported = dumpBin[0x9B]&InputPolarityFlipTxSupportedMask != 0 + caps.BankBroadcastSupported = dumpBin[0x9C]&BankBroadcastSupportedMask != 0 + caps.AutoSquelchDisableRxSupported = dumpBin[0x9C]&AutoSquelchDisableRxSupportedMask != 0 + caps.OutputDisableRxSupported = dumpBin[0x9C]&OutputDisableRxSupportedMask != 0 + caps.OutputPolarityFlipRxSupported = dumpBin[0x9C]&OutputPolarityFlipRxSupportedMask != 0 + + return e.state, nil +} diff --git a/internal/pkg/optic/sff8636/common.go b/internal/pkg/optic/sff8636/common.go new file mode 100644 index 0000000..5fb81a4 --- /dev/null +++ b/internal/pkg/optic/sff8636/common.go @@ -0,0 +1,35 @@ +package sff8636 + +import "slices" + +func checkSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { + var SFF8636CompatibleSFF8024IDs = [...]byte{ + 0x0D, // qsfp+ or later with sff8646 or sff8436 mgmt interface + 0x11, // qsfp28 or later with sff-8636 management interface + } + + var CompatibleSFF8638Versions = [...]byte{ + // 0x00 not compatible, do not use for rev 2.5 or higher + // 0x01 sff 8436 not compatible + // 0x02 sff 8436 not compatible + 0x03, // 1.3 or earlier + 0x04, // 1.4 + 0x05, // 1.5 + 0x06, // 2.0 + 0x07, // 2.5, 2.6 and 2.7 + 0x08, // 2.8, 2.9 and 2.10 + 0x09, // 2.11 + 0x0A, // 2.12 + } + + compatibleIdentifier := func(id byte) bool { return slices.Contains(SFF8636CompatibleSFF8024IDs[:], id) } + compatibleRev := func(sff8024rev byte) bool { return slices.Contains(CompatibleSFF8638Versions[:], sff8024rev) } + + // panic if revision is unknown + if compatibleIdentifier(sff8024Identifier) && !compatibleRev(sff8024Revision) { + panic(RefusalVerMismatch) + } + + // tells if compatible + return compatibleIdentifier(sff8024Identifier) +} diff --git a/internal/pkg/optic/sff8636/constants.go b/internal/pkg/optic/sff8636/constants.go new file mode 100644 index 0000000..a00c24e --- /dev/null +++ b/internal/pkg/optic/sff8636/constants.go @@ -0,0 +1,22 @@ +package sff8636 + +const PageSelectRegisterAddress = 0x7F + +const ( + PageSelectRegisterWriteErrorString = "I could not write to Page Select register, aborting program now." + RegisterWriteErrorString = "I could not write to arbitrary register, aborting program now." + PageNotAvailableTemplateErrorString = "Module responded that page 0x%x is not available, its likely I failed to " + + "correctly identify the module capabilities. Aborting program now." + RefusalVerMismatch = "This module has an " + + "SFF8636 Revision number that I do not know of, therefore it is unsupported. Program will be terminated now, " + + "as I cannot read nor write to this module without potential failure, data loss and/or equipment damage." +) + +// page 00 register 0x5D +const ( + SoftwareResetMask = 0b1000_0000 + EnableHighPowerClass8Mask = 0b0000_1000 + EnableHighPowerClass57Mask = 0b0000_0100 + LowPwrRequestSWMask = 0b0000_0010 + LowPwrOverrideMask = 0b0000_0001 +) diff --git a/internal/pkg/optic/sff8636/extension.go b/internal/pkg/optic/sff8636/extension.go new file mode 100644 index 0000000..a5d5e27 --- /dev/null +++ b/internal/pkg/optic/sff8636/extension.go @@ -0,0 +1,39 @@ +package sff8636 + +import "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" + +type ExtensionManagementStrategy struct { + state *pkg.ModuleState +} + +func NewSFF8636Extension(state *pkg.ModuleState) *ExtensionManagementStrategy { + return &ExtensionManagementStrategy{ + state: state, + } +} + +func (s2 *ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { + // implement SFF8636 specific pages here if needed in the future + return s2.state, nil +} + +func (s2 *ExtensionManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { + // implement SFF8636 specific pages here if needed in the future + return s2.state, nil +} + +func (s2 *ExtensionManagementStrategy) ManufacturerIsCompatibleWithProtocolExtension(_ string) bool { + return true // manufacturer names have 0 influence, only sff8024 does. so we defer decision +} + +func (s2 *ExtensionManagementStrategy) SFF8024IsCompatibleWithProtocolExtension( + sff8024Identifier byte, + sff8024Revision byte, +) bool { + return checkSFF8024(sff8024Identifier, sff8024Revision) +} + +func (s2 *ExtensionManagementStrategy) Activate() (*pkg.ModuleState, error) { + s2.state.SFF8636.Active = true + return s2.state, nil +} diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index 080c695..40bbdeb 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -2,7 +2,6 @@ package sff8636 import ( "fmt" - "slices" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" @@ -13,18 +12,8 @@ type ManagementStrategy struct { state *pkg.ModuleState } -type ExtensionManagementStrategy struct { - state *pkg.ModuleState -} - -const PageSelectRegisterAddress = 0x7F -const PageSelectRegisterWriteErrorString = "I could not write to Page Select register, aborting program now." -const RegisterWriteErrorString = "I could not write to arbitrary register, aborting program now." -const PageNotAvailableTemplateErrorString = "Module responded that page 0x%x is not available, its likely I failed to " + - "correctly identify the module capabilities. Aborting program now." - -func NewSFF8636Extension(state *pkg.ModuleState) *ExtensionManagementStrategy { - return &ExtensionManagementStrategy{ +func New(state *pkg.ModuleState) *ManagementStrategy { + return &ManagementStrategy{ state: state, } } @@ -71,81 +60,12 @@ func (s2 *ManagementStrategy) WritePageByteBin(page byte, _ byte, offset byte, v return nil } -func (s2 *ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { - // implement SFF8636 specific pages here if needed in the future - return s2.state, nil -} - -func (s2 *ExtensionManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { - // implement SFF8636 specific pages here if needed in the future - return s2.state, nil -} - -func (s2 *ExtensionManagementStrategy) ManufacturerIsCompatibleWithProtocolExtension(_ string) bool { - return true // manufacturer names have 0 influence, only sff8024 does. so we defer decision -} - -func (s2 *ExtensionManagementStrategy) SFF8024IsCompatibleWithProtocolExtension( - sff8024Identifier byte, - sff8024Revision byte, -) bool { - return checkSFF8024(sff8024Identifier, sff8024Revision) -} - -func (s2 *ExtensionManagementStrategy) Activate() (*pkg.ModuleState, error) { - s2.state.SFF8636.Active = true - return s2.state, nil -} - -func New(state *pkg.ModuleState) *ManagementStrategy { - return &ManagementStrategy{ - state: state, - } -} - -const RefusalVerMismatch = "This module has an " + - "SFF8636 Revision number that I do not know of, therefore it is unsupported. Program will be terminated now, " + - "as I cannot read nor write to this module without potential failure, data loss and/or equipment damage." - func (s2 *ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { return checkSFF8024(sff8024Identifier, sff8024Revision) } -func checkSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { - var SFF8636CompatibleSFF8024IDs = [...]byte{ - 0x0D, // qsfp+ or later with sff8646 or sff8436 mgmt interface - 0x11, // qsfp28 or later with sff-8636 management interface - } - - var CompatibleSFF8638Versions = [...]byte{ - // 0x00 not compatible, do not use for rev 2.5 or higher - // 0x01 sff 8436 not compatible - // 0x02 sff 8436 not compatible - 0x03, // 1.3 or earlier - 0x04, // 1.4 - 0x05, // 1.5 - 0x06, // 2.0 - 0x07, // 2.5, 2.6 and 2.7 - 0x08, // 2.8, 2.9 and 2.10 - 0x09, // 2.11 - 0x0A, // 2.12 - } - - compatibleIdentifier := func(id byte) bool { return slices.Contains(SFF8636CompatibleSFF8024IDs[:], id) } - compatibleRev := func(sff8024rev byte) bool { return slices.Contains(CompatibleSFF8638Versions[:], sff8024rev) } - - // panic if revision is unknown - if compatibleIdentifier(sff8024Identifier) && !compatibleRev(sff8024Revision) { - panic(RefusalVerMismatch) - } - - // tells if compatible - return compatibleIdentifier(sff8024Identifier) -} - func (s2 *ManagementStrategy) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { - //TODO implement me - panic("implement me") + return s2.state, nil // noop } func (s2 *ManagementStrategy) Get() (*pkg.ModuleState, error) { @@ -157,13 +77,6 @@ func (s2 *ManagementStrategy) Get() (*pkg.ModuleState, error) { } func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { - // register 0x5D lower mem masks - const SoftwareResetMask = 0b1000_0000 - const EnableHighPowerClass8Mask = 0b0000_1000 - const EnableHighPowerClass57Mask = 0b0000_0100 - const LowPwrRequestSWMask = 0b0000_0010 - const LowPwrOverrideMask = 0b0000_0001 - bin, err := s2.state.GetPageBin(0x00, 0) if err != nil { return nil, err @@ -189,6 +102,5 @@ func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, } func (s2 *ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { - //TODO implement me - panic("implement me") + return s2.state, nil // noop } diff --git a/internal/pkg/public.go b/internal/pkg/public.go index 073fa45..581953e 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -21,6 +21,57 @@ const ( Grid150String = "150.000" ) +const ( + CMISGridSpacing3p125Ghz = 0b0000_0000 + CMISGridSpacing6p25Ghz = 0b0001_0000 + CMISGridSpacing12p5Ghz = 0b0010_0000 + CMISGridSpacing25Ghz = 0b0011_0000 + CMISGridSpacing50Ghz = 0b0100_0000 + CMISGridSpacing100Ghz = 0b0101_0000 + CMISGridSpacing33Ghz = 0b0110_0000 + CMISGridSpacing75Ghz = 0b0111_0000 + CMISGridSpacing150Ghz = 0b1000_0000 + // CMISGridSpacingNotAvailable = 0b1111_0000 +) + +var CMISGridSpacingToFloatGhzMap = map[byte]float64{ + CMISGridSpacing3p125Ghz: 3.125, + CMISGridSpacing6p25Ghz: 6.25, + CMISGridSpacing12p5Ghz: 12.5, + CMISGridSpacing25Ghz: 25.0, + CMISGridSpacing50Ghz: 50.0, + CMISGridSpacing100Ghz: 100.0, + CMISGridSpacing33Ghz: 33.0, + CMISGridSpacing75Ghz: 75.0, + CMISGridSpacing150Ghz: 150.0, +} + +var FloatGhzToCMISGridSpacing = map[string]byte{ // cannot use float as key since not serializable + Grid3p125String: CMISGridSpacing3p125Ghz, + Grid6p250String: CMISGridSpacing6p25Ghz, + Grid12p5String: CMISGridSpacing12p5Ghz, + Grid25String: CMISGridSpacing25Ghz, + Grid50String: CMISGridSpacing50Ghz, + Grid100String: CMISGridSpacing100Ghz, + Grid33String: CMISGridSpacing33Ghz, + Grid75string: CMISGridSpacing75Ghz, + Grid150String: CMISGridSpacing150Ghz, +} + +const ( + CMISGridSpacingTxMask = 0b1111_0000 + CMISFineTuningEnableTxMask = 0b0000_0001 + CMISTuningInProgressTxMask = 0b0000_0010 + CMISWavelengthUnlockStatusTxMask = 0b0000_0001 + + CMISTargetOutputPowerOORFlagTxMask = 0b0010_0000 + CMISFineTuningOutOfRangeFlagTxMask = 0b0001_0000 + CMISTuningNotAcceptedFlagTxMask = 0b0000_1000 + CMISInvalidChannelNumberFlagTxMask = 0b0000_0100 + CMISWavelengthUnlockedFlagTxMask = 0b0000_0010 + CMISTuningCompleteFlagTxMask = 0b0000_0001 +) + // FinIsarCMISExtension client r/w interface for FinIsar specific settings // delegates concrete operations to strategy type FinIsarCMISExtension struct { @@ -146,57 +197,6 @@ type CMISLaserCapabilitiesAdvertising struct { ProgOutputPowerMax int16 `json:"prog_output_power_max"` // 0.001 dBm increments max power } -const ( - CMISGridSpacing3p125Ghz = 0b0000_0000 - CMISGridSpacing6p25Ghz = 0b0001_0000 - CMISGridSpacing12p5Ghz = 0b0010_0000 - CMISGridSpacing25Ghz = 0b0011_0000 - CMISGridSpacing50Ghz = 0b0100_0000 - CMISGridSpacing100Ghz = 0b0101_0000 - CMISGridSpacing33Ghz = 0b0110_0000 - CMISGridSpacing75Ghz = 0b0111_0000 - CMISGridSpacing150Ghz = 0b1000_0000 - // CMISGridSpacingNotAvailable = 0b1111_0000 -) - -var CMISGridSpacingToFloatGhzMap = map[byte]float64{ - CMISGridSpacing3p125Ghz: 3.125, - CMISGridSpacing6p25Ghz: 6.25, - CMISGridSpacing12p5Ghz: 12.5, - CMISGridSpacing25Ghz: 25.0, - CMISGridSpacing50Ghz: 50.0, - CMISGridSpacing100Ghz: 100.0, - CMISGridSpacing33Ghz: 33.0, - CMISGridSpacing75Ghz: 75.0, - CMISGridSpacing150Ghz: 150.0, -} - -var FloatGhzToCMISGridSpacing = map[string]byte{ // cannot use float as key since not serializable - Grid3p125String: CMISGridSpacing3p125Ghz, - Grid6p250String: CMISGridSpacing6p25Ghz, - Grid12p5String: CMISGridSpacing12p5Ghz, - Grid25String: CMISGridSpacing25Ghz, - Grid50String: CMISGridSpacing50Ghz, - Grid100String: CMISGridSpacing100Ghz, - Grid33String: CMISGridSpacing33Ghz, - Grid75string: CMISGridSpacing75Ghz, - Grid150String: CMISGridSpacing150Ghz, -} - -const ( - CMISGridSpacingTxMask = 0b1111_0000 - CMISFineTuningEnableTxMask = 0b0000_0001 - CMISTuningInProgressTxMask = 0b0000_0010 - CMISWavelengthUnlockStatusTxMask = 0b0000_0001 - - CMISTargetOutputPowerOORFlagTxMask = 0b0010_0000 - CMISFineTuningOutOfRangeFlagTxMask = 0b0001_0000 - CMISTuningNotAcceptedFlagTxMask = 0b0000_1000 - CMISInvalidChannelNumberFlagTxMask = 0b0000_0100 - CMISWavelengthUnlockedFlagTxMask = 0b0000_0010 - CMISTuningCompleteFlagTxMask = 0b0000_0001 -) - type CMISBankedTunableLaserControlAndStatus struct { // page 12h tunable laser control and status From 7b6c3c4c2d711a4cae423e8a98dc305a1f174f57 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 24 Jun 2026 18:15:05 +0200 Subject: [PATCH 27/36] remove unneeded arguments --- cmd/optic-programmer.go | 2 +- internal/pkg/optic/cmis/cmis.go | 4 ++-- internal/pkg/optic/cmis/extension.go | 2 +- internal/pkg/optic/default/default.go | 4 ++-- internal/pkg/optic/sff8636/extension.go | 2 +- internal/pkg/optic/sff8636/flexoptix.go | 2 +- internal/pkg/optic/sff8636/sff8636.go | 4 ++-- internal/pkg/public.go | 18 +++++++++--------- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/cmd/optic-programmer.go b/cmd/optic-programmer.go index be19a45..e814106 100644 --- a/cmd/optic-programmer.go +++ b/cmd/optic-programmer.go @@ -239,7 +239,7 @@ func main() { extension.CtrlStatus[bank].GridSpacingTx[lane] = pkg.FloatGhzToCMISGridSpacing[gridSpacingStr] extension.CtrlStatus[bank].ChannelNumberTx[lane] = channel - _, err := module.SetExtensionsState(module) + _, err := module.SetExtensionsState() if err != nil { return err } diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index 288b33a..ea6e1a2 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -78,7 +78,7 @@ func (s2 *ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revi return checkSFF8024(sff8024Identifier, sff8024Revision) } -func (s2 *ManagementStrategy) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) Set() (*pkg.ModuleState, error) { return s2.state, nil // noop } @@ -137,7 +137,7 @@ func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, return s2.state, nil } -func (s2 *ManagementStrategy) SetAdministrativeInformation(_ *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) SetAdministrativeInformation() (*pkg.ModuleState, error) { // noop return s2.state, nil } diff --git a/internal/pkg/optic/cmis/extension.go b/internal/pkg/optic/cmis/extension.go index 5909c4e..8b835be 100644 --- a/internal/pkg/optic/cmis/extension.go +++ b/internal/pkg/optic/cmis/extension.go @@ -47,7 +47,7 @@ func (e *ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, err return e.state, nil } -func (e *ExtensionManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (e *ExtensionManagementStrategy) SetExtensionState() (*pkg.ModuleState, error) { e.assumeConfigChange() _, err := e.SetTunableLaserControlStatus(e.state) diff --git a/internal/pkg/optic/default/default.go b/internal/pkg/optic/default/default.go index 0131378..f722cc5 100644 --- a/internal/pkg/optic/default/default.go +++ b/internal/pkg/optic/default/default.go @@ -36,7 +36,7 @@ func (d *ManagementStrategy) WritePageByteBin(_ byte, _ byte, _ byte, _ byte) er panic(genericWriteErrorString) } -func (d *ManagementStrategy) Set(_ *pkg.ModuleState) (*pkg.ModuleState, error) { +func (d *ManagementStrategy) Set() (*pkg.ModuleState, error) { panic(genericWriteErrorString) } @@ -56,7 +56,7 @@ func (d *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, e return d.state, nil } -func (d *ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (d *ManagementStrategy) SetAdministrativeInformation() (*pkg.ModuleState, error) { panic(genericWriteErrorString) } diff --git a/internal/pkg/optic/sff8636/extension.go b/internal/pkg/optic/sff8636/extension.go index a5d5e27..80d9643 100644 --- a/internal/pkg/optic/sff8636/extension.go +++ b/internal/pkg/optic/sff8636/extension.go @@ -17,7 +17,7 @@ func (s2 *ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, er return s2.state, nil } -func (s2 *ExtensionManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 *ExtensionManagementStrategy) SetExtensionState() (*pkg.ModuleState, error) { // implement SFF8636 specific pages here if needed in the future return s2.state, nil } diff --git a/internal/pkg/optic/sff8636/flexoptix.go b/internal/pkg/optic/sff8636/flexoptix.go index 4bebfd7..fa22378 100644 --- a/internal/pkg/optic/sff8636/flexoptix.go +++ b/internal/pkg/optic/sff8636/flexoptix.go @@ -40,7 +40,7 @@ func (f *FlexOptixSFF8636ManagementStrategy) GetExtensionState() (*pkg.ModuleSta return f.state, nil } -func (f *FlexOptixSFF8636ManagementStrategy) SetExtensionState(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (f *FlexOptixSFF8636ManagementStrategy) SetExtensionState() (*pkg.ModuleState, error) { // laser capabilities advertising is all-fields read-only so, no-op for this one _, err := cmis.SetTunableLaserControlStatus( diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index 40bbdeb..2f34ad0 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -64,7 +64,7 @@ func (s2 *ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revi return checkSFF8024(sff8024Identifier, sff8024Revision) } -func (s2 *ManagementStrategy) Set(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) Set() (*pkg.ModuleState, error) { return s2.state, nil // noop } @@ -101,6 +101,6 @@ func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, return s2.state, nil } -func (s2 *ManagementStrategy) SetAdministrativeInformation(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) SetAdministrativeInformation() (*pkg.ModuleState, error) { return s2.state, nil // noop } diff --git a/internal/pkg/public.go b/internal/pkg/public.go index 581953e..9342953 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -362,8 +362,8 @@ func (m *ModuleState) ToJson() ([]byte, error) { return marshal, nil } -func (m *ModuleState) Set(s *ModuleState) (*ModuleState, error) { - return m.mgmtProtoConcreteStrategy.Set(s) +func (m *ModuleState) Set() (*ModuleState, error) { + return m.mgmtProtoConcreteStrategy.Set() } func (m *ModuleState) Get() (*ModuleState, error) { @@ -378,13 +378,13 @@ func (m *ModuleState) GetAdministrativeInformation() (*ModuleState, error) { return m.mgmtProtoConcreteStrategy.GetAdministrativeInformation() } -func (m *ModuleState) SetAdministrativeInformation(s *ModuleState) (*ModuleState, error) { - return m.mgmtProtoConcreteStrategy.SetAdministrativeInformation(s) +func (m *ModuleState) SetAdministrativeInformation() (*ModuleState, error) { + return m.mgmtProtoConcreteStrategy.SetAdministrativeInformation() } -func (m *ModuleState) SetExtensionsState(s *ModuleState) (*ModuleState, error) { +func (m *ModuleState) SetExtensionsState() (*ModuleState, error) { for _, e := range m.mgmtProtoExtensionsConcreteStrategies { - _, err := e.SetExtensionState(s) + _, err := e.SetExtensionState() if err != nil { return nil, err } @@ -406,10 +406,10 @@ func (m *ModuleState) GetExtensionsState() (*ModuleState, error) { type Management interface { GetPageBin(page byte, bank byte) ([]byte, error) // GetPageBin page is mandatory, bank is optional as it is only used in CMIS WritePageByteBin(page byte, bank byte, offset byte, value byte) error // WritePageByteBin page is mandatory, bank is optional as it is only used in CMIS - Set(s *ModuleState) (*ModuleState, error) + Set() (*ModuleState, error) Get() (*ModuleState, error) GetAdministrativeInformation() (*ModuleState, error) - SetAdministrativeInformation(s *ModuleState) (*ModuleState, error) + SetAdministrativeInformation() (*ModuleState, error) } // ProtocolExtensionManagement is a generic interface for protocol extensions, @@ -418,7 +418,7 @@ type Management interface { // no need for the added complexity of Generics in this case. type ProtocolExtensionManagement interface { GetExtensionState() (*ModuleState, error) - SetExtensionState(s *ModuleState) (*ModuleState, error) + SetExtensionState() (*ModuleState, error) Activate() (*ModuleState, error) } From eb63d9b2046bfacdb534293eabc24fa191218e03 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Thu, 25 Jun 2026 13:30:51 +0200 Subject: [PATCH 28/36] add reset and low power commands --- cmd/optic-programmer.go | 51 +++++++++++++++++++++++++++ internal/pkg/optic/cmis/cmis.go | 20 ++++++++++- internal/pkg/optic/cmis/constants.go | 2 ++ internal/pkg/optic/sff8636/sff8636.go | 21 +++++++++-- internal/pkg/public.go | 2 +- 5 files changed, 92 insertions(+), 4 deletions(-) diff --git a/cmd/optic-programmer.go b/cmd/optic-programmer.go index e814106..de01ab3 100644 --- a/cmd/optic-programmer.go +++ b/cmd/optic-programmer.go @@ -2,6 +2,8 @@ package main import ( "context" + "errors" + "fmt" "log/slog" "os" "strconv" @@ -178,6 +180,55 @@ func main() { }, }, }, + { + Name: "reset", + Usage: "WARNING, YOU MAY LOSE CONFIG!!!! request module software reset", + Action: ActionTemplateMethod(restrictedFeatureSetFactory, func( + module *pkg.ModuleState, + context context.Context, + command *cli.Command, + ) error { + module.SoftwareReset = true + + _, err := module.SetAdministrativeInformation() + if err != nil { + return err + } + + println(OK) + return nil + }), + }, + { + Name: "low-power", + Usage: "toggle low power mode on/off", + Action: ActionTemplateMethod(restrictedFeatureSetFactory, func( + module *pkg.ModuleState, + context context.Context, + command *cli.Command, + ) error { + if command.Args().Len() != 1 { + return errors.New("please provide on/off argument") + } + toggle := command.Args().Get(0) + + if toggle == "on" { + module.LowPwrRequestSW = true + } else if toggle == "off" { + module.LowPwrRequestSW = false + } else { + return errors.New(fmt.Sprintf("cannot parse on/off argument %s", toggle)) + } + + _, err := module.SetAdministrativeInformation() + if err != nil { + return err + } + + println(OK) + return nil + }), + }, { Name: "set", Aliases: []string{"s"}, diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index ea6e1a2..bfc379a 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -79,6 +79,10 @@ func (s2 *ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revi } func (s2 *ManagementStrategy) Set() (*pkg.ModuleState, error) { + _, err := s2.SetAdministrativeInformation() + if err != nil { + return nil, err + } return s2.state, nil // noop } @@ -99,6 +103,8 @@ func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, s2.state.ManagementProtocol = "cmis" // lower mem + s2.state.LowPwrRequestSW = dumpBin[0x1A]&LowPwrRequestMask != 0 + s2.state.SoftwareReset = dumpBin[0x1A]&SoftwareResetMask != 0 s2.state.CMIS.MemoryModelPaged = dumpBin[0x02]&MemoryModelMask == 0 // 0 is paged memory, 1 is flat s2.state.CMIS.SteppedConfigOnly = dumpBin[0x02]&SteppedConfigOnlyMask == 0 s2.state.CMIS.I2CMciMaxSpeedKhz = I2CMciMaxSpeedToKhz[dumpBin[0x02]&I2CMciMaxSpeedMask] @@ -138,6 +144,18 @@ func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, } func (s2 *ManagementStrategy) SetAdministrativeInformation() (*pkg.ModuleState, error) { - // noop + dumpBin, err := s2.state.GetPageBin(0x00, 0x00) + if err != nil { + return nil, err + } + + dumpBin[0x1A] |= LowPwrRequestMask & util.YesNoByte(s2.state.LowPwrRequestSW) + dumpBin[0x1A] |= SoftwareResetMask & util.YesNoByte(s2.state.SoftwareReset) + + err = s2.state.WritePageBin(0x00, 0, dumpBin) + if err != nil { + return nil, err + } + return s2.state, nil } diff --git a/internal/pkg/optic/cmis/constants.go b/internal/pkg/optic/cmis/constants.go index 3ef8a74..06a56df 100644 --- a/internal/pkg/optic/cmis/constants.go +++ b/internal/pkg/optic/cmis/constants.go @@ -21,6 +21,8 @@ const ( ) const ( + SoftwareResetMask = 0b0000_1000 + LowPwrRequestMask = 0b0001_0000 ProgOutputPowerPerLaneSupported = 0b1000_0000 MemoryModelMask = 0b1000_0000 SteppedConfigOnlyMask = 0b0100_0000 diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index 2f34ad0..14277ef 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -65,6 +65,10 @@ func (s2 *ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revi } func (s2 *ManagementStrategy) Set() (*pkg.ModuleState, error) { + _, err := s2.SetAdministrativeInformation() + if err != nil { + return nil, err + } return s2.state, nil // noop } @@ -89,7 +93,7 @@ func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, s2.state.SoftwareReset = bin[0x5D]&SoftwareResetMask == SoftwareResetMask s2.state.SFF8636.EnableHighPowerClass8 = bin[0x5D]&EnableHighPowerClass8Mask == EnableHighPowerClass8Mask s2.state.SFF8636.EnableHighPowerClass57 = bin[0x5D]&EnableHighPowerClass57Mask == EnableHighPowerClass57Mask - s2.state.SFF8636.LowPwrRequestSW = bin[0x5D]&LowPwrRequestSWMask == LowPwrRequestSWMask + s2.state.LowPwrRequestSW = bin[0x5D]&LowPwrRequestSWMask == LowPwrRequestSWMask s2.state.SFF8636.LowPwrOverride = bin[0x5D]&LowPwrOverrideMask == LowPwrOverrideMask // page 0x00 @@ -102,5 +106,18 @@ func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, } func (s2 *ManagementStrategy) SetAdministrativeInformation() (*pkg.ModuleState, error) { - return s2.state, nil // noop + bin, err := s2.state.GetPageBin(0x00, 0) + if err != nil { + return nil, err + } + + bin[0x5D] |= LowPwrRequestSWMask & util.YesNoByte(s2.state.LowPwrRequestSW) + bin[0x5D] |= SoftwareResetMask & util.YesNoByte(s2.state.SoftwareReset) + + err = s2.state.WritePageBin(0x00, 0, bin) + if err != nil { + return nil, err + } + + return s2.state, nil } diff --git a/internal/pkg/public.go b/internal/pkg/public.go index 9342953..cd6d97c 100644 --- a/internal/pkg/public.go +++ b/internal/pkg/public.go @@ -242,7 +242,6 @@ type SFF8636OnlyExtension struct { // lower mem EnableHighPowerClass8 bool `json:"enable_high_power_class_8"` EnableHighPowerClass57 bool `json:"enable_high_power_class_57"` - LowPwrRequestSW bool `json:"low_pwr_request_sw"` LowPwrOverride bool `json:"low_pwr_override"` } @@ -264,6 +263,7 @@ type ModuleState struct { ManagementProtocol string `json:"management_protocol"` SFF8024Identifier uint8 `json:"sff_8024_identifier"` // SFF8024Identifier lower mem public read-only sff8024 id field SFF8024Revision uint8 `json:"sff_8024_revision"` // SFF8024Revision lower mem public read-only sff8024 revision id field + LowPwrRequestSW bool `json:"low_pwr_request_sw"` SoftwareReset bool `json:"software_reset"` // page 00 region, common info between From aa815a8885f97b51b76be1b426830a89498dc0cc Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Thu, 25 Jun 2026 13:36:37 +0200 Subject: [PATCH 29/36] fix binary logic bug --- internal/pkg/optic/cmis/common.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/pkg/optic/cmis/common.go b/internal/pkg/optic/cmis/common.go index 600e6ed..f8c28d8 100644 --- a/internal/pkg/optic/cmis/common.go +++ b/internal/pkg/optic/cmis/common.go @@ -96,7 +96,7 @@ func SetTunableLaserControlStatus( for i := 0; i <= maxN; i += 1 { // rewrite to mem - dumpBin[0x80+i] = (caps.GridSpacingTx[i] & pkg.CMISGridSpacingTxMask) & + dumpBin[0x80+i] = (caps.GridSpacingTx[i] & pkg.CMISGridSpacingTxMask) | (util.YesNoByte(caps.FineTuningEnableTx[i]) & pkg.CMISFineTuningEnableTxMask) util.WriteBeInt16(caps.ChannelNumberTx[i], dumpBin, 0x88+byte(2*i)) @@ -106,11 +106,11 @@ func SetTunableLaserControlStatus( util.WriteBeInt16(caps.TargetOutputPowerTx[i], dumpBin, 0xC8+byte(2*i)) // subsequent fields read-only - dumpBin[0xEF+i] = (util.YesNoByte(caps.TargetOutputPowerOORMaskTx[i]) & pkg.CMISTargetOutputPowerOORFlagTxMask) & - (util.YesNoByte(caps.FineTuningPowerOutOfRangeMaskTx[i]) & pkg.CMISFineTuningOutOfRangeFlagTxMask) & - (util.YesNoByte(caps.TuningNotAcceptedMaskTx[i]) & pkg.CMISTuningNotAcceptedFlagTxMask) & - (util.YesNoByte(caps.InvalidChannelMaskTx[i]) & pkg.CMISInvalidChannelNumberFlagTxMask) & - (util.YesNoByte(caps.WavelengthUnlockedMaskTx[i]) & pkg.CMISWavelengthUnlockedFlagTxMask) & + dumpBin[0xEF+i] = (util.YesNoByte(caps.TargetOutputPowerOORMaskTx[i]) & pkg.CMISTargetOutputPowerOORFlagTxMask) | + (util.YesNoByte(caps.FineTuningPowerOutOfRangeMaskTx[i]) & pkg.CMISFineTuningOutOfRangeFlagTxMask) | + (util.YesNoByte(caps.TuningNotAcceptedMaskTx[i]) & pkg.CMISTuningNotAcceptedFlagTxMask) | + (util.YesNoByte(caps.InvalidChannelMaskTx[i]) & pkg.CMISInvalidChannelNumberFlagTxMask) | + (util.YesNoByte(caps.WavelengthUnlockedMaskTx[i]) & pkg.CMISWavelengthUnlockedFlagTxMask) | (util.YesNoByte(caps.TuningCompleteMaskTx[i]) & pkg.CMISTuningCompleteFlagTxMask) } From fff303dfd41747a19341e7641e2db5f08bda283e Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Thu, 25 Jun 2026 16:37:03 +0200 Subject: [PATCH 30/36] fix missing admin ctrl bit clears --- internal/pkg/optic/cmis/cmis.go | 1 + internal/pkg/optic/sff8636/sff8636.go | 1 + 2 files changed, 2 insertions(+) diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/pkg/optic/cmis/cmis.go index bfc379a..00d0000 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/pkg/optic/cmis/cmis.go @@ -149,6 +149,7 @@ func (s2 *ManagementStrategy) SetAdministrativeInformation() (*pkg.ModuleState, return nil, err } + dumpBin[0x1A] &^= LowPwrRequestMask | SoftwareResetMask // clear dumpBin[0x1A] |= LowPwrRequestMask & util.YesNoByte(s2.state.LowPwrRequestSW) dumpBin[0x1A] |= SoftwareResetMask & util.YesNoByte(s2.state.SoftwareReset) diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/pkg/optic/sff8636/sff8636.go index 14277ef..0df8a9b 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/pkg/optic/sff8636/sff8636.go @@ -111,6 +111,7 @@ func (s2 *ManagementStrategy) SetAdministrativeInformation() (*pkg.ModuleState, return nil, err } + bin[0x5D] &^= LowPwrRequestSWMask | SoftwareResetMask // clear bin[0x5D] |= LowPwrRequestSWMask & util.YesNoByte(s2.state.LowPwrRequestSW) bin[0x5D] |= SoftwareResetMask & util.YesNoByte(s2.state.SoftwareReset) From d99ff75350f18e8a5a4d1874ff0c207e61cf3813 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Thu, 25 Jun 2026 17:54:00 +0200 Subject: [PATCH 31/36] move to subcommand --- cmd/optic-programmer.go | 70 ++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/cmd/optic-programmer.go b/cmd/optic-programmer.go index de01ab3..0d14dca 100644 --- a/cmd/optic-programmer.go +++ b/cmd/optic-programmer.go @@ -119,6 +119,10 @@ func main() { } cmd := &cli.Command{ + Copyright: "WDZ GmbH 2026", + Usage: "in-field optical module programming for rtbrick", + Description: "CMIS & SFF8636 optics programmer for rtbrick remote devices. Only works with RBFS,\n" + + "uses SMBus & i2c utils to issue direct i2c commands to optical modules.", Flags: []cli.Flag{ &cli.StringFlag{ Name: "user", @@ -136,7 +140,7 @@ func main() { Commands: []*cli.Command{ { Name: "show", - Usage: "Shows information about an optic in a specific device", + Usage: "show information about an optic in a specific device. show basic (failsafe) or show all.", Commands: []*cli.Command{ { Name: "basic", @@ -199,44 +203,44 @@ func main() { return nil }), }, - { - Name: "low-power", - Usage: "toggle low power mode on/off", - Action: ActionTemplateMethod(restrictedFeatureSetFactory, func( - module *pkg.ModuleState, - context context.Context, - command *cli.Command, - ) error { - if command.Args().Len() != 1 { - return errors.New("please provide on/off argument") - } - toggle := command.Args().Get(0) - - if toggle == "on" { - module.LowPwrRequestSW = true - } else if toggle == "off" { - module.LowPwrRequestSW = false - } else { - return errors.New(fmt.Sprintf("cannot parse on/off argument %s", toggle)) - } - - _, err := module.SetAdministrativeInformation() - if err != nil { - return err - } - - println(OK) - return nil - }), - }, { Name: "set", Aliases: []string{"s"}, Usage: "sets parameter", Commands: []*cli.Command{ { - Name: "dwdmgrid", - Description: "For host-programmable DWDM modules, sets channel from grid", + Name: "low-power", + Usage: "toggle low power mode on/off", + Action: ActionTemplateMethod(restrictedFeatureSetFactory, func( + module *pkg.ModuleState, + context context.Context, + command *cli.Command, + ) error { + if command.Args().Len() != 1 { + return errors.New("please provide on/off argument") + } + toggle := command.Args().Get(0) + + if toggle == "on" { + module.LowPwrRequestSW = true + } else if toggle == "off" { + module.LowPwrRequestSW = false + } else { + return errors.New(fmt.Sprintf("cannot parse on/off argument %s", toggle)) + } + + _, err := module.SetAdministrativeInformation() + if err != nil { + return err + } + + println(OK) + return nil + }), + }, + { + Name: "dwdmgrid", + Usage: "sets dwdm grid mode and channel", Flags: []cli.Flag{ &cli.Float64Flag{ Name: "grid-spacing", From 58954c7566f1609ae7ae13482c7a92962156fcdf Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Thu, 25 Jun 2026 17:54:09 +0200 Subject: [PATCH 32/36] documentation --- README.md | 150 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 88 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 74f42ee..a6768b7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ # rtbrick-optic-programmer -A project to programm Finisar DWDM optics plugged into an RtBrick device. +CMIS & SFF8636 optics programmer for rtbrick remote devices. Features a JSON output for machine parsing. +Tested with Finisar CMIS optics, Finisar SFF8636, FlexOptix SFF8636 C-band programmable. + +You are expected to have already set up your ssh key on the device, password entry is currently not supported. # Building @@ -14,71 +17,94 @@ Set the `LOG_LEVEL` env var to "debug" to view i2c dumps as they are read. # Usage -```shell -./optic-programmer show --user horst lrmd0001.infra.lab.wobcom.de ifp-0/0/2 -2026/01/09 16:54:56 Running command=sudo i2cset -y 33 0x50 127 0 -2026/01/09 16:54:56 Running command=sudo i2cdump -y 33 0x50 b -2026/01/09 16:54:57 Running command=sudo i2cset -y 33 0x50 127 0 -2026/01/09 16:54:57 Running command=sudo i2cset -y 33 0x50 127 18 -2026/01/09 16:54:57 Running command=sudo i2cdump -y 33 0x50 b -2026/01/09 16:54:58 Running command=sudo i2cset -y 33 0x50 127 0 -2026/01/09 16:54:58 Running command=sudo i2cset -y 33 0x50 127 30 -2026/01/09 16:54:58 Running command=sudo i2cdump -y 33 0x50 b -2026/01/09 16:54:59 Running command=sudo i2cset -y 33 0x50 127 0 -2026/01/09 16:54:59 Running command=sudo i2cset -y 33 0x50 127 27 -2026/01/09 16:54:59 Running command=sudo i2cdump -y 33 0x50 b -2026/01/09 16:55:00 Running command=sudo i2cset -y 33 0x50 127 0 -2026/01/09 16:55:00 Vendor Name: FINISAR CORP. -2026/01/09 16:55:00 Vendor PN: FTLC3351R3PL1 -2026/01/09 16:55:00 Vendor SN: 2511W1653 -2026/01/09 16:55:00 Grid Spacing: 100 -2026/01/09 16:55:00 Frequency Offset: -9 -2026/01/09 16:55:00 Frequency: 192.2 THz -2026/01/09 16:55:00 Channel: 22 -2026/01/09 16:55:00 Flex Tune Enabled: false -2026/01/09 16:55:00 Low Power Mode: false -2026/01/09 16:55:00 Nominal Wavelength Control Enabled: false +## Manual ``` +NAME: + optic-programmer - in-field optical module programming for rtbrick + +USAGE: + optic-programmer [global options] [command [command options]] + +DESCRIPTION: + CMIS & SFF8636 optics programmer for rtbrick remote devices. Only works with RBFS, + uses SMBus & i2c utils to issue direct i2c commands to optical modules. + +COMMANDS: + show show information about an optic in a specific device. show basic (failsafe) or show all. + reset WARNING, YOU MAY LOSE CONFIG!!!! request module software reset + set, s sets parameter + set low-power toggle low power mode on/off + set dwdmgrid sets dwdm grid mode and channel + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS: + --user string [$USER] + --device string [$DEVICE] + --interface string [$INTERFACE] + --help, -h show help + +COPYRIGHT: + WDZ GmbH 2026 +``` +## Examples + +### Showing basic information +Show basic only shows standard protocol information. Manufacturer protocol extensions are not used with +this command. You can get a detailed output, including protocol and manufacturer extensions, +using the command `show all`. ```shell -./optic-programmer program --user jwagner lrma0002.infra.lab.wobcom.de ifp-0/0/2 --grid-spacing=100 --channel=22 -2026/01/09 16:53:22 Running command=sudo i2cset -y 11 0x50 127 0 -2026/01/09 16:53:22 Running command=sudo i2cdump -y 11 0x50 b -2026/01/09 16:53:23 Running command=sudo i2cset -y 11 0x50 127 0 -2026/01/09 16:53:23 Running command=sudo i2cset -y 11 0x50 127 18 -2026/01/09 16:53:23 Running command=sudo i2cdump -y 11 0x50 b -2026/01/09 16:53:23 Running command=sudo i2cset -y 11 0x50 127 0 -2026/01/09 16:53:24 Running command=sudo i2cset -y 11 0x50 127 30 -2026/01/09 16:53:24 Running command=sudo i2cdump -y 11 0x50 b -2026/01/09 16:53:24 Running command=sudo i2cset -y 11 0x50 127 0 -2026/01/09 16:53:24 Running command=sudo i2cset -y 11 0x50 127 27 -2026/01/09 16:53:25 Running command=sudo i2cdump -y 11 0x50 b -2026/01/09 16:53:25 Running command=sudo i2cset -y 11 0x50 127 0 -2026/01/09 16:53:25 Vendor Name: FINISAR CORP. -2026/01/09 16:53:25 Vendor PN: FTLC3351R3PL1 -2026/01/09 16:53:25 Vendor SN: 2511W1695 -2026/01/09 16:53:25 Setting Low Power Mode... -2026/01/09 16:53:25 Running command=sudo i2cset -y 11 0x50 127 0 -2026/01/09 16:53:25 Running command=sudo i2cset -y 11 0x50 93 2 -2026/01/09 16:53:26 Running command=sudo i2cset -y 11 0x50 127 0 -2026/01/09 16:53:27 Flex Tune is already disabled... -2026/01/09 16:53:27 Grid Spacing is already programmed at 100 GHz, no programming needed... -2026/01/09 16:53:27 Channel must be programmed to 22, currently 37 -2026/01/09 16:53:27 Running command=sudo i2cset -y 11 0x50 127 18 -2026/01/09 16:53:27 Running command=sudo i2cset -y 11 0x50 137 247 -2026/01/09 16:53:27 Running command=sudo i2cset -y 11 0x50 127 0 -2026/01/09 16:53:27 Running command=sudo i2cset -y 11 0x50 127 18 -2026/01/09 16:53:27 Running command=sudo i2cset -y 11 0x50 136 255 -2026/01/09 16:53:27 Running command=sudo i2cset -y 11 0x50 127 0 -2026/01/09 16:53:28 Setting Nominal Wavelength Control Programming... -2026/01/09 16:53:28 Running command=sudo i2cset -y 11 0x50 127 176 -2026/01/09 16:53:28 Running command=sudo i2cset -y 11 0x50 129 1 -2026/01/09 16:53:28 Running command=sudo i2cset -y 11 0x50 127 0 -2026/01/09 16:53:29 Enabling High Power Mode... -2026/01/09 16:53:29 Running command=sudo i2cset -y 11 0x50 127 0 -2026/01/09 16:53:30 Running command=sudo i2cset -y 11 0x50 93 4 -2026/01/09 16:53:30 Running command=sudo i2cset -y 11 0x50 127 0 +./optic-programmer --user llecrivain --device lrma0001.infra.lab.wobcom.de --interface ifp-0/0/2 show basic +{ + "sff8636": { + "active": true, + "enable_high_power_class_8": false, + "enable_high_power_class_57": true, + "low_pwr_override": true + }, + "management_protocol": "sff8636", + "sff_8024_identifier": 17, + "sff_8024_revision": 8, + "low_pwr_request_sw": false, + "software_reset": false, + "vendor_name": "FLEXOPTIX", + "vendor_part_number": "Q.16S1HG.14.O2D", + "vendor_part_revision": "Q.16S1HG.14.O2D", + "vendor_serial_number": "FQM0023" +} +``` + +### Programming the tunable laser +Lane and bank are optional, by default the tool will always select first bank, first lane or 1st lane (when +lanes are not banked). Be aware that not specifying grid spacing will reset it to default (100Ghz). +```shell +./optic-programmer --user llecrivain --device lrma0001.infra.lab.wobcom.de --interface ifp-0/0/0 set dwdmgrid \ + --grid-spacing 50 --channel -20 --lane 1 --bank 1 +module has processed command. check module status. ``` +## Some jq examples for filtering output +### Checking tuning progress status for lane 0 + +Tuning complete flag will clear when being read. +```shell +./optic-programmer --user llecrivain --device lrma0001.infra.lab.wobcom.de --interface ifp-0/0/0 show all | \ +jq "\ +.cmis.tunable_laser.control_status[0] | +{ + grid_spacing_tx: .grid_spacing_tx[0], + channel_number_tx: .channel_number_tx[0], + current_laser_frequency_mhz_tx: .current_laser_frequency_mhz_tx[0], + current_laser_frequency_mhz_tx: .current_laser_frequency_mhz_tx[0], + tuning_in_progress_tx: .tuning_in_progress_tx[0], + tuning_complete_flag_tx: .tuning_complete_flag_tx[0] +}" + +``` +### Only show CMIS tunable laser capabilities +```shell +./optic-programmer --user llecrivain --device lrma0001.infra.lab.wobcom.de --interface ifp-0/0/0 show all | \ +jq ".cmis.tunable_laser.capabilities" +``` From 14e34affadfd71d0aa29ea8c4f344f71e6facb16 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 1 Jul 2026 11:44:01 +0200 Subject: [PATCH 33/36] restructure directory/package layout --- cmd/optic-programmer.go | 68 +++--- internal/{pkg => }/optic/cmis/cmis.go | 35 ++-- internal/optic/cmis/common.go | 193 ++++++++++++++++++ internal/{pkg => }/optic/cmis/constants.go | 0 internal/{pkg => }/optic/cmis/extension.go | 24 +-- internal/{pkg => }/optic/default/default.go | 17 +- internal/{pkg/public.go => optic/optic.go} | 6 +- internal/{pkg => }/optic/sff8636/common.go | 0 internal/{pkg => }/optic/sff8636/constants.go | 0 internal/{pkg => }/optic/sff8636/extension.go | 14 +- internal/{pkg => }/optic/sff8636/flexoptix.go | 16 +- internal/{pkg => }/optic/sff8636/sff8636.go | 31 +-- internal/{pkg => }/optic/util/ascii.go | 0 internal/{pkg => }/optic/util/bin.go | 0 internal/{pkg => }/optic/util/bin_test.go | 0 internal/{pkg => }/optic/util/slice.go | 0 internal/pkg/optic/cmis/common.go | 193 ------------------ internal/{pkg => }/rtbrick/rtbrick.go | 0 internal/{pkg => }/rtbrick/ssh/connection.go | 2 +- internal/{pkg => }/rtbrick/ssh/i2c_handle.go | 0 internal/{pkg => }/util.go | 2 +- 21 files changed, 303 insertions(+), 298 deletions(-) rename internal/{pkg => }/optic/cmis/cmis.go (79%) create mode 100644 internal/optic/cmis/common.go rename internal/{pkg => }/optic/cmis/constants.go (100%) rename internal/{pkg => }/optic/cmis/extension.go (86%) rename internal/{pkg => }/optic/default/default.go (76%) rename internal/{pkg/public.go => optic/optic.go} (99%) rename internal/{pkg => }/optic/sff8636/common.go (100%) rename internal/{pkg => }/optic/sff8636/constants.go (100%) rename internal/{pkg => }/optic/sff8636/extension.go (63%) rename internal/{pkg => }/optic/sff8636/flexoptix.go (77%) rename internal/{pkg => }/optic/sff8636/sff8636.go (72%) rename internal/{pkg => }/optic/util/ascii.go (100%) rename internal/{pkg => }/optic/util/bin.go (100%) rename internal/{pkg => }/optic/util/bin_test.go (100%) rename internal/{pkg => }/optic/util/slice.go (100%) delete mode 100644 internal/pkg/optic/cmis/common.go rename internal/{pkg => }/rtbrick/rtbrick.go (100%) rename internal/{pkg => }/rtbrick/ssh/connection.go (98%) rename internal/{pkg => }/rtbrick/ssh/i2c_handle.go (100%) rename internal/{pkg => }/util.go (97%) diff --git a/cmd/optic-programmer.go b/cmd/optic-programmer.go index 0d14dca..ef00f20 100644 --- a/cmd/optic-programmer.go +++ b/cmd/optic-programmer.go @@ -10,11 +10,11 @@ import ( "strings" "github.com/urfave/cli/v3" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/cmis" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/default" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/sff8636" - connection "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick/ssh" + "github.com/wobcom/rtbrick-optic-programmer/internal/optic" + cmis2 "github.com/wobcom/rtbrick-optic-programmer/internal/optic/cmis" + "github.com/wobcom/rtbrick-optic-programmer/internal/optic/default" + sff8637 "github.com/wobcom/rtbrick-optic-programmer/internal/optic/sff8636" + "github.com/wobcom/rtbrick-optic-programmer/internal/rtbrick/ssh" ) const OK = "module has processed command. check module status." @@ -33,39 +33,39 @@ func getLoglevel(level string) slog.Level { panic("invalid log level provided") } -var concreteManagementStrategies = [...]func(state *pkg.ModuleState) pkg.ConcreteManagementStrategy{ - func(state *pkg.ModuleState) pkg.ConcreteManagementStrategy { return sff8636.New(state) }, - func(state *pkg.ModuleState) pkg.ConcreteManagementStrategy { return cmis.New(state) }, +var concreteManagementStrategies = [...]func(state *optic.ModuleState) optic.ConcreteManagementStrategy{ + func(state *optic.ModuleState) optic.ConcreteManagementStrategy { return sff8637.New(state) }, + func(state *optic.ModuleState) optic.ConcreteManagementStrategy { return cmis2.New(state) }, } -var concreteExtensionStrategies = [...]func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy{ - func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy { - return sff8636.NewSFF8636Extension(state) +var concreteExtensionStrategies = [...]func(state *optic.ModuleState) optic.ConcreteExtensionManagementStrategy{ + func(state *optic.ModuleState) optic.ConcreteExtensionManagementStrategy { + return sff8637.NewSFF8636Extension(state) }, - func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy { - return sff8636.NewFlexOptixSFF8636Extension(state) + func(state *optic.ModuleState) optic.ConcreteExtensionManagementStrategy { + return sff8637.NewFlexOptixSFF8636Extension(state) }, - func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy { - return cmis.NewCMISExtension(state) + func(state *optic.ModuleState) optic.ConcreteExtensionManagementStrategy { + return cmis2.NewCMISExtension(state) }, } -var safeModeConcreteExtensionStrategies = [...]func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy{ +var safeModeConcreteExtensionStrategies = [...]func(state *optic.ModuleState) optic.ConcreteExtensionManagementStrategy{ // no manufacturers enabled, only lower mem and page 00 - func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy { - return sff8636.NewSFF8636Extension(state) + func(state *optic.ModuleState) optic.ConcreteExtensionManagementStrategy { + return sff8637.NewSFF8636Extension(state) }, - func(state *pkg.ModuleState) pkg.ConcreteExtensionManagementStrategy { - return cmis.NewCMISExtension(state) + func(state *optic.ModuleState) optic.ConcreteExtensionManagementStrategy { + return cmis2.NewCMISExtension(state) }, } -var defaultManagementStrategy = func(state *pkg.ModuleState) pkg.ConcreteManagementStrategy { +var defaultManagementStrategy = func(state *optic.ModuleState) optic.ConcreteManagementStrategy { return _default.New(state) } -var restrictedFeatureSetFactory = func(handle *connection.I2cRWHandle) *pkg.ModuleState { - return pkg.NewModuleState( +var restrictedFeatureSetFactory = func(handle *connection.I2cRWHandle) *optic.ModuleState { + return optic.NewModuleState( defaultManagementStrategy, concreteManagementStrategies[:], safeModeConcreteExtensionStrategies[:], @@ -73,8 +73,8 @@ var restrictedFeatureSetFactory = func(handle *connection.I2cRWHandle) *pkg.Modu ) } -var allFeatureSetFactory = func(handle *connection.I2cRWHandle) *pkg.ModuleState { - return pkg.NewModuleState( +var allFeatureSetFactory = func(handle *connection.I2cRWHandle) *optic.ModuleState { + return optic.NewModuleState( defaultManagementStrategy, concreteManagementStrategies[:], concreteExtensionStrategies[:], @@ -86,8 +86,8 @@ var allFeatureSetFactory = func(handle *connection.I2cRWHandle) *pkg.ModuleState // module will always have basic administrative data (safe lower memory and safe page 00) fetched // before being passed to callback with the rest of the other arguments. func ActionTemplateMethod( - moduleFactory func(handle *connection.I2cRWHandle) *pkg.ModuleState, - call func(module *pkg.ModuleState, context context.Context, cmd *cli.Command) error) cli.ActionFunc { + moduleFactory func(handle *connection.I2cRWHandle) *optic.ModuleState, + call func(module *optic.ModuleState, context context.Context, cmd *cli.Command) error) cli.ActionFunc { return func(context context.Context, cmd *cli.Command) error { user := cmd.String("user") router := cmd.String("device") @@ -145,7 +145,7 @@ func main() { { Name: "basic", Action: ActionTemplateMethod(restrictedFeatureSetFactory, func( - module *pkg.ModuleState, + module *optic.ModuleState, context context.Context, command *cli.Command, ) error { @@ -163,7 +163,7 @@ func main() { { Name: "all", Action: ActionTemplateMethod(allFeatureSetFactory, func( - module *pkg.ModuleState, + module *optic.ModuleState, context context.Context, command *cli.Command, ) error { @@ -188,7 +188,7 @@ func main() { Name: "reset", Usage: "WARNING, YOU MAY LOSE CONFIG!!!! request module software reset", Action: ActionTemplateMethod(restrictedFeatureSetFactory, func( - module *pkg.ModuleState, + module *optic.ModuleState, context context.Context, command *cli.Command, ) error { @@ -212,7 +212,7 @@ func main() { Name: "low-power", Usage: "toggle low power mode on/off", Action: ActionTemplateMethod(restrictedFeatureSetFactory, func( - module *pkg.ModuleState, + module *optic.ModuleState, context context.Context, command *cli.Command, ) error { @@ -263,7 +263,7 @@ func main() { }, }, Action: ActionTemplateMethod(allFeatureSetFactory, func( - module *pkg.ModuleState, + module *optic.ModuleState, context context.Context, command *cli.Command, ) error { @@ -280,7 +280,7 @@ func main() { // putting it here cos I need to capture args. var setChannelAndGrid = func( - extension *pkg.CommonTunableLaserFields, + extension *optic.CommonTunableLaserFields, ) error { if !extension.Capabilities.SupportedGridSpacings[gridSpacingStr] { panic("Module does not support this frequency.") @@ -291,7 +291,7 @@ func main() { panic("target offset is above or below maximum frequencies for this grid.") } - extension.CtrlStatus[bank].GridSpacingTx[lane] = pkg.FloatGhzToCMISGridSpacing[gridSpacingStr] + extension.CtrlStatus[bank].GridSpacingTx[lane] = optic.FloatGhzToCMISGridSpacing[gridSpacingStr] extension.CtrlStatus[bank].ChannelNumberTx[lane] = channel _, err := module.SetExtensionsState() diff --git a/internal/pkg/optic/cmis/cmis.go b/internal/optic/cmis/cmis.go similarity index 79% rename from internal/pkg/optic/cmis/cmis.go rename to internal/optic/cmis/cmis.go index 00d0000..1ba4c57 100644 --- a/internal/pkg/optic/cmis/cmis.go +++ b/internal/optic/cmis/cmis.go @@ -3,15 +3,16 @@ package cmis import ( "fmt" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" + "github.com/wobcom/rtbrick-optic-programmer/internal" + "github.com/wobcom/rtbrick-optic-programmer/internal/optic" + util2 "github.com/wobcom/rtbrick-optic-programmer/internal/optic/util" ) type ManagementStrategy struct { - state *pkg.ModuleState + state *optic.ModuleState } -func New(state *pkg.ModuleState) *ManagementStrategy { +func New(state *optic.ModuleState) *ManagementStrategy { return &ManagementStrategy{ state: state, } @@ -46,7 +47,7 @@ func (s2 *ManagementStrategy) GetPageBin(page byte, bank byte) ([]byte, error) { if err != nil { return []byte{}, err } - dumpBin := pkg.ParseI2CDump(*pageStr) + dumpBin := internal.ParseI2CDump(*pageStr) // check page + bank select was authorized only and only if target page select = page select if dumpBin[PageSelectRegisterAddress] != page { @@ -78,7 +79,7 @@ func (s2 *ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revi return checkSFF8024(sff8024Identifier, sff8024Revision) } -func (s2 *ManagementStrategy) Set() (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) Set() (*optic.ModuleState, error) { _, err := s2.SetAdministrativeInformation() if err != nil { return nil, err @@ -86,7 +87,7 @@ func (s2 *ManagementStrategy) Set() (*pkg.ModuleState, error) { return s2.state, nil // noop } -func (s2 *ManagementStrategy) Get() (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) Get() (*optic.ModuleState, error) { _, err := s2.GetAdministrativeInformation() if err != nil { return nil, err @@ -94,7 +95,7 @@ func (s2 *ManagementStrategy) Get() (*pkg.ModuleState, error) { return s2.state, nil } -func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) GetAdministrativeInformation() (*optic.ModuleState, error) { dumpBin, err := s2.state.GetPageBin(0x00, 0x00) if err != nil { return nil, err @@ -115,13 +116,13 @@ func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, s2.state.CMIS.AutoCommissioningHot = AutoCommissioning == 0b0010 // 0x11 is reserved // page 00 - s2.state.VendorName = util.ParseASCIIToString(dumpBin[0x81:0x90]) + s2.state.VendorName = util2.ParseASCIIToString(dumpBin[0x81:0x90]) s2.state.CMIS.VendorOUI = dumpBin[0x91:0x93] - s2.state.VendorPartNumber = util.ParseASCIIToString(dumpBin[0x94:0xA3]) - s2.state.VendorPartRevision = util.ParseASCIIToString(dumpBin[0xA4:0xA5]) - s2.state.VendorSerialNumber = util.ParseASCIIToString(dumpBin[0xA6:0xB5]) - s2.state.CMIS.DateCode = util.ParseASCIIToString(dumpBin[0xB6:0xBD]) - s2.state.CMIS.CLEICode = util.ParseASCIIToString(dumpBin[0xBE:0xC7]) + s2.state.VendorPartNumber = util2.ParseASCIIToString(dumpBin[0x94:0xA3]) + s2.state.VendorPartRevision = util2.ParseASCIIToString(dumpBin[0xA4:0xA5]) + s2.state.VendorSerialNumber = util2.ParseASCIIToString(dumpBin[0xA6:0xB5]) + s2.state.CMIS.DateCode = util2.ParseASCIIToString(dumpBin[0xB6:0xBD]) + s2.state.CMIS.CLEICode = util2.ParseASCIIToString(dumpBin[0xBE:0xC7]) s2.state.CMIS.PowerClass = PowerClassToInt[dumpBin[0xC8]&ModulePowerClassMask] s2.state.CMIS.MaxPowerWatts = 0.25 * float64(dumpBin[0xC9]) // byte is interpreted as uint8. unit is ceil of quarter-watts @@ -143,15 +144,15 @@ func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, return s2.state, nil } -func (s2 *ManagementStrategy) SetAdministrativeInformation() (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) SetAdministrativeInformation() (*optic.ModuleState, error) { dumpBin, err := s2.state.GetPageBin(0x00, 0x00) if err != nil { return nil, err } dumpBin[0x1A] &^= LowPwrRequestMask | SoftwareResetMask // clear - dumpBin[0x1A] |= LowPwrRequestMask & util.YesNoByte(s2.state.LowPwrRequestSW) - dumpBin[0x1A] |= SoftwareResetMask & util.YesNoByte(s2.state.SoftwareReset) + dumpBin[0x1A] |= LowPwrRequestMask & util2.YesNoByte(s2.state.LowPwrRequestSW) + dumpBin[0x1A] |= SoftwareResetMask & util2.YesNoByte(s2.state.SoftwareReset) err = s2.state.WritePageBin(0x00, 0, dumpBin) if err != nil { diff --git a/internal/optic/cmis/common.go b/internal/optic/cmis/common.go new file mode 100644 index 0000000..dc7c31b --- /dev/null +++ b/internal/optic/cmis/common.go @@ -0,0 +1,193 @@ +package cmis + +import ( + "slices" + + "github.com/wobcom/rtbrick-optic-programmer/internal/optic" + "github.com/wobcom/rtbrick-optic-programmer/internal/optic/util" +) + +func checkSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { + var CmisCompatibleSFF8024IDs = [...]byte{ + 0x1E, // qsfp+ or later with cmis + 0x1F, // sfp-dd with cmis + 0x20, // sfp+ or later with cmis + 0x21, // osfp-xd with cmis + 0x22, // oif-elfs with cmis + 0x23, // 4 lanes cdfp with cmis + 0x24, // 8 lanes cdfp with cmis + 0x25, // 16 lanes cdfp with cmis + 0x18, // qsfp-dd 8x - may support cmis + } + + const MajVerMask byte = 0xF0 // upper nibble is bits 7-4 per cmis rev 5.38 section 8.2.1 + const MinMajVer byte = 0x50 // minimum supported major version is 5 + + compatibleIdentifier := func(id byte) bool { return slices.Contains(CmisCompatibleSFF8024IDs[:], id) } + compatibleMajorRev := func(sff8024rev byte) bool { return (sff8024rev & MajVerMask) <= MinMajVer } + + // panic if CMIS and revision is above our maximum + if compatibleIdentifier(sff8024Identifier) && !compatibleMajorRev(sff8024Revision) { + panic(RefusalMajorTooHigh) + } + + // tells if compatible + return compatibleIdentifier(sff8024Identifier) +} + +// GetTunableLaserControlStatus maxN between 0 and 7 (channel N, used for only-channel-0 access) +func GetTunableLaserControlStatus( + state *optic.ModuleState, + caps *optic.CMISBankedTunableLaserControlAndStatus, + bank byte, + maxN int, +) (*optic.ModuleState, error) { + dumpBin, err := state.GetPageBin(TunableLaserControlStatusPage, bank) + if err != nil { + return nil, err + } + + for i := 0; i <= maxN; i += 1 { + caps.GridSpacingTx[i] = dumpBin[0x80+i] & optic.CMISGridSpacingTxMask + caps.GridSpacingTxROGhz[i] = optic.CMISGridSpacingToFloatGhzMap[caps.GridSpacingTx[i]] + caps.FineTuningEnableTx[i] = dumpBin[0x80+i]&optic.CMISFineTuningEnableTxMask != 0 + + caps.ChannelNumberTx[i] = util.ReadBeInt16(dumpBin, 0x88+byte(2*i)) // S16 over 2 bytes + caps.FineTuningOffsetMhzTx[i] = util.ReadBeInt16(dumpBin, 0x98+byte(2*i)) + caps.CurrentLaserFrequencyMhzTx[i] = util.ReadBeUint32(dumpBin, 0xA8+byte(4*i)) // U32 over 4 bytes, units Mhz + + caps.TargetOutputPowerTx[i] = util.ReadBeInt16(dumpBin, 0xC8+byte(2*i)) + + caps.TuningInProgressTx[i] = dumpBin[0xDE+i]&optic.CMISTuningInProgressTxMask != 0 + caps.WaveLengthUnlockStatus[i] = dumpBin[0xDE+i]&optic.CMISWavelengthUnlockStatusTxMask != 0 + + // per spec: the bit n-1 is set if and only if any of the latched flags are set to 1 + caps.LaserTuningFlagSummaryTx[i] = (dumpBin[0xE6+i] & (0b0000_0001 << i)) != 0 + + caps.TargetOutputPowerOORFlagTx[i] = dumpBin[0xE7+i]&optic.CMISTargetOutputPowerOORFlagTxMask != 0 + caps.FineTuningOutOfRangeFlagTx[i] = dumpBin[0xE7+i]&optic.CMISFineTuningOutOfRangeFlagTxMask != 0 + caps.TuningNotAcceptedMaskTx[i] = dumpBin[0xE7+i]&optic.CMISTuningNotAcceptedFlagTxMask != 0 + caps.InvalidChannelNumberFlagTx[i] = dumpBin[0xE7+i]&optic.CMISInvalidChannelNumberFlagTxMask != 0 + caps.WavelengthUnlockedFlagTx[i] = dumpBin[0xE7+i]&optic.CMISWavelengthUnlockedFlagTxMask != 0 + caps.TuningCompleteFlagTx[i] = dumpBin[0xE7+i]&optic.CMISTuningCompleteFlagTxMask != 0 + + // reading interrupt masks, bitfield pattern same as alarm + caps.TargetOutputPowerOORMaskTx[i] = dumpBin[0xEF+i]&optic.CMISTargetOutputPowerOORFlagTxMask != 0 + caps.FineTuningPowerOutOfRangeMaskTx[i] = dumpBin[0xEF+i]&optic.CMISFineTuningOutOfRangeFlagTxMask != 0 + caps.TuningNotAcceptedMaskTx[i] = dumpBin[0xEF+i]&optic.CMISTuningNotAcceptedFlagTxMask != 0 + caps.InvalidChannelMaskTx[i] = dumpBin[0xEF+i]&optic.CMISInvalidChannelNumberFlagTxMask != 0 + caps.WavelengthUnlockedMaskTx[i] = dumpBin[0xEF+i]&optic.CMISWavelengthUnlockedFlagTxMask != 0 + caps.TuningCompleteMaskTx[i] = dumpBin[0xEF+i]&optic.CMISTuningCompleteFlagTxMask != 0 + } + + return state, nil +} + +func SetTunableLaserControlStatus( + state *optic.ModuleState, + caps *optic.CMISBankedTunableLaserControlAndStatus, + bank byte, + maxN int, +) (*optic.ModuleState, error) { + dumpBin, err := state.GetPageBin(TunableLaserControlStatusPage, bank) + if err != nil { + return nil, err + } + + for i := 0; i <= maxN; i += 1 { + // rewrite to mem + dumpBin[0x80+i] = (caps.GridSpacingTx[i] & optic.CMISGridSpacingTxMask) | + (util.YesNoByte(caps.FineTuningEnableTx[i]) & optic.CMISFineTuningEnableTxMask) + + util.WriteBeInt16(caps.ChannelNumberTx[i], dumpBin, 0x88+byte(2*i)) + util.WriteBeInt16(caps.FineTuningOffsetMhzTx[i], dumpBin, 0x98+byte(2*i)) + // no write for CurrentLaserFrequency, read-only + + util.WriteBeInt16(caps.TargetOutputPowerTx[i], dumpBin, 0xC8+byte(2*i)) + // subsequent fields read-only + + dumpBin[0xEF+i] = (util.YesNoByte(caps.TargetOutputPowerOORMaskTx[i]) & optic.CMISTargetOutputPowerOORFlagTxMask) | + (util.YesNoByte(caps.FineTuningPowerOutOfRangeMaskTx[i]) & optic.CMISFineTuningOutOfRangeFlagTxMask) | + (util.YesNoByte(caps.TuningNotAcceptedMaskTx[i]) & optic.CMISTuningNotAcceptedFlagTxMask) | + (util.YesNoByte(caps.InvalidChannelMaskTx[i]) & optic.CMISInvalidChannelNumberFlagTxMask) | + (util.YesNoByte(caps.WavelengthUnlockedMaskTx[i]) & optic.CMISWavelengthUnlockedFlagTxMask) | + (util.YesNoByte(caps.TuningCompleteMaskTx[i]) & optic.CMISTuningCompleteFlagTxMask) + } + + err = state.WritePageBin(TunableLaserControlStatusPage, bank, dumpBin) + if err != nil { + return nil, err + } + + return state, nil +} + +func GetLaserCapabilitiesAdvertising( + state *optic.ModuleState, + caps *optic.CMISLaserCapabilitiesAdvertising, +) (*optic.ModuleState, error) { + dumpBin, err := state.GetPageBin(0x04, 0x00) // non-banked + if err != nil { + return nil, err + } + + caps.SupportedGridSpacings = make(map[string]bool) + + // I really hate that Go doesn't have bitfields. + caps.SupportedGridSpacings[optic.Grid75string] = dumpBin[0x80]&GridSupported75GhzMask != 0 + caps.SupportedGridSpacings[optic.Grid33String] = dumpBin[0x80]&GridSupported33GhzMask != 0 + caps.SupportedGridSpacings[optic.Grid100String] = dumpBin[0x80]&GridSupported100GhzMask != 0 + caps.SupportedGridSpacings[optic.Grid50String] = dumpBin[0x80]&GridSupported50GhzMask != 0 + caps.SupportedGridSpacings[optic.Grid25String] = dumpBin[0x80]&GridSupported25GhzMask != 0 + caps.SupportedGridSpacings[optic.Grid12p5String] = dumpBin[0x80]&GridSupported12p5GhzMask != 0 + caps.SupportedGridSpacings[optic.Grid6p250String] = dumpBin[0x80]&GridSupported6p25GhzMask != 0 + caps.SupportedGridSpacings[optic.Grid3p125String] = dumpBin[0x80]&GridSupported3p125GhzMask != 0 + caps.SupportedGridSpacings[optic.Grid150String] = dumpBin[0x81]&GridSupported150GhzMask != 0 + + caps.FineTuningSupported = dumpBin[0x81]&FineTuningSupportedMask != 0 + + caps.GridLowChannel = make(map[string]int16) + caps.GridHighChannel = make(map[string]int16) + + var base byte = 0x82 + // 3.125Ghz + caps.GridLowChannel[optic.Grid3p125String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[optic.Grid3p125String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 6.25Ghz + caps.GridLowChannel[optic.Grid6p250String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[optic.Grid6p250String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 12.5Ghz + caps.GridLowChannel[optic.Grid12p5String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[optic.Grid12p5String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 25Ghz + caps.GridLowChannel[optic.Grid25String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[optic.Grid25String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 50Ghz + caps.GridLowChannel[optic.Grid50String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[optic.Grid50String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 100Ghz + caps.GridLowChannel[optic.Grid100String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[optic.Grid100String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 33Ghz + caps.GridLowChannel[optic.Grid33String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[optic.Grid33String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 75Ghz + caps.GridLowChannel[optic.Grid75string] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[optic.Grid75string] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + // 150Ghz + caps.GridLowChannel[optic.Grid150String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.GridHighChannel[optic.Grid150String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) + + base = 0xBE // skip reserved region + caps.FineTuningResolution = util.ReadBeUint16AndShiftBase(dumpBin, &base) + caps.FineTuningLowOffset = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.FineTuningHighOffset = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.ProgOutputPowerPerLaneSupported = dumpBin[base]&ProgOutputPowerPerLaneSupported != 0 + + base = 0xC6 // skip reserved region + caps.ProgOutputPowerMin = util.ReadBeInt16AndShiftBase(dumpBin, &base) + caps.ProgOutputPowerMax = util.ReadBeInt16AndShiftBase(dumpBin, &base) + + // TODO checksum support + return state, nil +} diff --git a/internal/pkg/optic/cmis/constants.go b/internal/optic/cmis/constants.go similarity index 100% rename from internal/pkg/optic/cmis/constants.go rename to internal/optic/cmis/constants.go diff --git a/internal/pkg/optic/cmis/extension.go b/internal/optic/cmis/extension.go similarity index 86% rename from internal/pkg/optic/cmis/extension.go rename to internal/optic/cmis/extension.go index 8b835be..53d0bac 100644 --- a/internal/pkg/optic/cmis/extension.go +++ b/internal/optic/cmis/extension.go @@ -1,15 +1,15 @@ package cmis import ( - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" + "github.com/wobcom/rtbrick-optic-programmer/internal/optic" + "github.com/wobcom/rtbrick-optic-programmer/internal/optic/util" ) type ExtensionManagementStrategy struct { - state *pkg.ModuleState + state *optic.ModuleState } -func NewCMISExtension(state *pkg.ModuleState) *ExtensionManagementStrategy { +func NewCMISExtension(state *optic.ModuleState) *ExtensionManagementStrategy { return &ExtensionManagementStrategy{ state: state, } @@ -30,7 +30,7 @@ func (e *ExtensionManagementStrategy) assumeConfigChange() { } } -func (e *ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { +func (e *ExtensionManagementStrategy) GetExtensionState() (*optic.ModuleState, error) { _, err := e.GetSupportedControlsAdvertising() if err != nil { return nil, err @@ -47,7 +47,7 @@ func (e *ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, err return e.state, nil } -func (e *ExtensionManagementStrategy) SetExtensionState() (*pkg.ModuleState, error) { +func (e *ExtensionManagementStrategy) SetExtensionState() (*optic.ModuleState, error) { e.assumeConfigChange() _, err := e.SetTunableLaserControlStatus(e.state) @@ -69,12 +69,12 @@ func (e *ExtensionManagementStrategy) SFF8024IsCompatibleWithProtocolExtension( return checkSFF8024(sff8024Identifier, sff8024Revision) } -func (e *ExtensionManagementStrategy) Activate() (*pkg.ModuleState, error) { +func (e *ExtensionManagementStrategy) Activate() (*optic.ModuleState, error) { e.state.CMIS.Active = true return e.state, nil } -func (e *ExtensionManagementStrategy) GetTunableLaserControlStatus() (*pkg.ModuleState, error) { +func (e *ExtensionManagementStrategy) GetTunableLaserControlStatus() (*optic.ModuleState, error) { e.assumeHigherPagesReadable() var bank byte @@ -82,7 +82,7 @@ func (e *ExtensionManagementStrategy) GetTunableLaserControlStatus() (*pkg.Modul for bank = 0x00; bank <= e.state.CMIS.SupportedControls.MaximumBankSupported; bank += 1 { e.state.CMIS.TunableLaser.CtrlStatus = append( e.state.CMIS.TunableLaser.CtrlStatus, - pkg.CMISBankedTunableLaserControlAndStatus{}, + optic.CMISBankedTunableLaserControlAndStatus{}, ) // adding banks on the go to avoid having max banks all the time _, err := GetTunableLaserControlStatus( e.state, &e.state.CMIS.TunableLaser.CtrlStatus[bank], bank, MaximumLaneNumber, @@ -95,7 +95,7 @@ func (e *ExtensionManagementStrategy) GetTunableLaserControlStatus() (*pkg.Modul return e.state, nil } -func (e *ExtensionManagementStrategy) SetTunableLaserControlStatus(s *pkg.ModuleState) (*pkg.ModuleState, error) { +func (e *ExtensionManagementStrategy) SetTunableLaserControlStatus(s *optic.ModuleState) (*optic.ModuleState, error) { e.assumeHigherPagesReadable() for i, bank := range e.state.CMIS.TunableLaser.CtrlStatus { @@ -108,7 +108,7 @@ func (e *ExtensionManagementStrategy) SetTunableLaserControlStatus(s *pkg.Module return e.state, nil } -func (e *ExtensionManagementStrategy) GetLaserCapabilitiesAdvertising() (*pkg.ModuleState, error) { +func (e *ExtensionManagementStrategy) GetLaserCapabilitiesAdvertising() (*optic.ModuleState, error) { e.assumeHigherPagesReadable() _, err := GetLaserCapabilitiesAdvertising( @@ -121,7 +121,7 @@ func (e *ExtensionManagementStrategy) GetLaserCapabilitiesAdvertising() (*pkg.Mo return e.state, nil } -func (e *ExtensionManagementStrategy) GetSupportedControlsAdvertising() (*pkg.ModuleState, error) { +func (e *ExtensionManagementStrategy) GetSupportedControlsAdvertising() (*optic.ModuleState, error) { e.assumeHigherPagesReadable() dumpBin, err := e.state.GetPageBin(0x01, 0x00) diff --git a/internal/pkg/optic/default/default.go b/internal/optic/default/default.go similarity index 76% rename from internal/pkg/optic/default/default.go rename to internal/optic/default/default.go index f722cc5..79a5bdb 100644 --- a/internal/pkg/optic/default/default.go +++ b/internal/optic/default/default.go @@ -1,17 +1,18 @@ package _default import ( - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" + "github.com/wobcom/rtbrick-optic-programmer/internal" + "github.com/wobcom/rtbrick-optic-programmer/internal/optic" ) // ManagementStrategy is the default Management strategy which should be set at initialization // it can only read page00, and does 0 write. It can tell which concrete strategy which you should // be using by looking at identifier codes and constructor ASCII type ManagementStrategy struct { - state *pkg.ModuleState + state *optic.ModuleState } -func New(state *pkg.ModuleState) *ManagementStrategy { +func New(state *optic.ModuleState) *ManagementStrategy { return &ManagementStrategy{ state: state, } @@ -29,22 +30,22 @@ func (d *ManagementStrategy) GetPageBin(_ byte, _ byte) ([]byte, error) { if err != nil { return []byte{}, err } - return pkg.ParseI2CDump(*pageStr), nil + return internal.ParseI2CDump(*pageStr), nil } func (d *ManagementStrategy) WritePageByteBin(_ byte, _ byte, _ byte, _ byte) error { panic(genericWriteErrorString) } -func (d *ManagementStrategy) Set() (*pkg.ModuleState, error) { +func (d *ManagementStrategy) Set() (*optic.ModuleState, error) { panic(genericWriteErrorString) } -func (d *ManagementStrategy) Get() (*pkg.ModuleState, error) { +func (d *ManagementStrategy) Get() (*optic.ModuleState, error) { panic(genericReadErrorString) } -func (d *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { +func (d *ManagementStrategy) GetAdministrativeInformation() (*optic.ModuleState, error) { bin, err := d.state.GetPageBin(0x00, 0) if err != nil { return nil, err @@ -56,7 +57,7 @@ func (d *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, e return d.state, nil } -func (d *ManagementStrategy) SetAdministrativeInformation() (*pkg.ModuleState, error) { +func (d *ManagementStrategy) SetAdministrativeInformation() (*optic.ModuleState, error) { panic(genericWriteErrorString) } diff --git a/internal/pkg/public.go b/internal/optic/optic.go similarity index 99% rename from internal/pkg/public.go rename to internal/optic/optic.go index cd6d97c..44ed04d 100644 --- a/internal/pkg/public.go +++ b/internal/optic/optic.go @@ -1,12 +1,12 @@ -package pkg +package optic import ( "encoding/json" "fmt" "reflect" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick/ssh" + "github.com/wobcom/rtbrick-optic-programmer/internal/optic/util" + "github.com/wobcom/rtbrick-optic-programmer/internal/rtbrick/ssh" ) const ( diff --git a/internal/pkg/optic/sff8636/common.go b/internal/optic/sff8636/common.go similarity index 100% rename from internal/pkg/optic/sff8636/common.go rename to internal/optic/sff8636/common.go diff --git a/internal/pkg/optic/sff8636/constants.go b/internal/optic/sff8636/constants.go similarity index 100% rename from internal/pkg/optic/sff8636/constants.go rename to internal/optic/sff8636/constants.go diff --git a/internal/pkg/optic/sff8636/extension.go b/internal/optic/sff8636/extension.go similarity index 63% rename from internal/pkg/optic/sff8636/extension.go rename to internal/optic/sff8636/extension.go index 80d9643..5a75298 100644 --- a/internal/pkg/optic/sff8636/extension.go +++ b/internal/optic/sff8636/extension.go @@ -1,23 +1,25 @@ package sff8636 -import "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" +import ( + "github.com/wobcom/rtbrick-optic-programmer/internal/optic" +) type ExtensionManagementStrategy struct { - state *pkg.ModuleState + state *optic.ModuleState } -func NewSFF8636Extension(state *pkg.ModuleState) *ExtensionManagementStrategy { +func NewSFF8636Extension(state *optic.ModuleState) *ExtensionManagementStrategy { return &ExtensionManagementStrategy{ state: state, } } -func (s2 *ExtensionManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { +func (s2 *ExtensionManagementStrategy) GetExtensionState() (*optic.ModuleState, error) { // implement SFF8636 specific pages here if needed in the future return s2.state, nil } -func (s2 *ExtensionManagementStrategy) SetExtensionState() (*pkg.ModuleState, error) { +func (s2 *ExtensionManagementStrategy) SetExtensionState() (*optic.ModuleState, error) { // implement SFF8636 specific pages here if needed in the future return s2.state, nil } @@ -33,7 +35,7 @@ func (s2 *ExtensionManagementStrategy) SFF8024IsCompatibleWithProtocolExtension( return checkSFF8024(sff8024Identifier, sff8024Revision) } -func (s2 *ExtensionManagementStrategy) Activate() (*pkg.ModuleState, error) { +func (s2 *ExtensionManagementStrategy) Activate() (*optic.ModuleState, error) { s2.state.SFF8636.Active = true return s2.state, nil } diff --git a/internal/pkg/optic/sff8636/flexoptix.go b/internal/optic/sff8636/flexoptix.go similarity index 77% rename from internal/pkg/optic/sff8636/flexoptix.go rename to internal/optic/sff8636/flexoptix.go index fa22378..f16c814 100644 --- a/internal/pkg/optic/sff8636/flexoptix.go +++ b/internal/optic/sff8636/flexoptix.go @@ -3,23 +3,23 @@ package sff8636 import ( "regexp" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/cmis" + "github.com/wobcom/rtbrick-optic-programmer/internal/optic" + "github.com/wobcom/rtbrick-optic-programmer/internal/optic/cmis" ) type FlexOptixSFF8636ManagementStrategy struct { - state *pkg.ModuleState + state *optic.ModuleState } var ValidFlexOptixVendorName = regexp.MustCompile(`(?i)^flexoptix$`) -func NewFlexOptixSFF8636Extension(state *pkg.ModuleState) *FlexOptixSFF8636ManagementStrategy { +func NewFlexOptixSFF8636Extension(state *optic.ModuleState) *FlexOptixSFF8636ManagementStrategy { return &FlexOptixSFF8636ManagementStrategy{ state: state, } } -func (f *FlexOptixSFF8636ManagementStrategy) GetExtensionState() (*pkg.ModuleState, error) { +func (f *FlexOptixSFF8636ManagementStrategy) GetExtensionState() (*optic.ModuleState, error) { _, err := cmis.GetLaserCapabilitiesAdvertising(f.state, &f.state.FlexOptixSFF8636.TunableLaser.Capabilities) if err != nil { return nil, err @@ -28,7 +28,7 @@ func (f *FlexOptixSFF8636ManagementStrategy) GetExtensionState() (*pkg.ModuleSta f.state.FlexOptixSFF8636.TunableLaser.CtrlStatus = nil f.state.FlexOptixSFF8636.TunableLaser.CtrlStatus = append( f.state.FlexOptixSFF8636.TunableLaser.CtrlStatus, - pkg.CMISBankedTunableLaserControlAndStatus{}, + optic.CMISBankedTunableLaserControlAndStatus{}, ) _, err = cmis.GetTunableLaserControlStatus( f.state, &f.state.FlexOptixSFF8636.TunableLaser.CtrlStatus[0], 0x00, 0, @@ -40,7 +40,7 @@ func (f *FlexOptixSFF8636ManagementStrategy) GetExtensionState() (*pkg.ModuleSta return f.state, nil } -func (f *FlexOptixSFF8636ManagementStrategy) SetExtensionState() (*pkg.ModuleState, error) { +func (f *FlexOptixSFF8636ManagementStrategy) SetExtensionState() (*optic.ModuleState, error) { // laser capabilities advertising is all-fields read-only so, no-op for this one _, err := cmis.SetTunableLaserControlStatus( @@ -61,7 +61,7 @@ func (f *FlexOptixSFF8636ManagementStrategy) SFF8024IsCompatibleWithProtocolExte return checkSFF8024(sff8024Identifier, sff8024Revision) } -func (f *FlexOptixSFF8636ManagementStrategy) Activate() (*pkg.ModuleState, error) { +func (f *FlexOptixSFF8636ManagementStrategy) Activate() (*optic.ModuleState, error) { f.state.FlexOptixSFF8636.Active = true return f.state, nil } diff --git a/internal/pkg/optic/sff8636/sff8636.go b/internal/optic/sff8636/sff8636.go similarity index 72% rename from internal/pkg/optic/sff8636/sff8636.go rename to internal/optic/sff8636/sff8636.go index 0df8a9b..68676bb 100644 --- a/internal/pkg/optic/sff8636/sff8636.go +++ b/internal/optic/sff8636/sff8636.go @@ -3,16 +3,17 @@ package sff8636 import ( "fmt" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" + "github.com/wobcom/rtbrick-optic-programmer/internal" + "github.com/wobcom/rtbrick-optic-programmer/internal/optic" + util2 "github.com/wobcom/rtbrick-optic-programmer/internal/optic/util" ) // ManagementStrategy SFF8636 is a concrete implementation of the Management interface for SFF8636 spec type ManagementStrategy struct { - state *pkg.ModuleState + state *optic.ModuleState } -func New(state *pkg.ModuleState) *ManagementStrategy { +func New(state *optic.ModuleState) *ManagementStrategy { return &ManagementStrategy{ state: state, } @@ -32,7 +33,7 @@ func (s2 *ManagementStrategy) GetPageBin(page byte, _ byte) ([]byte, error) { if err != nil { return []byte{}, err } - dumpBin := pkg.ParseI2CDump(*pageStr) + dumpBin := internal.ParseI2CDump(*pageStr) // then check Page Select was authorized by reading back Page Select register if dumpBin[PageSelectRegisterAddress] != page { @@ -64,7 +65,7 @@ func (s2 *ManagementStrategy) AcceptsSFF8024(sff8024Identifier byte, sff8024Revi return checkSFF8024(sff8024Identifier, sff8024Revision) } -func (s2 *ManagementStrategy) Set() (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) Set() (*optic.ModuleState, error) { _, err := s2.SetAdministrativeInformation() if err != nil { return nil, err @@ -72,7 +73,7 @@ func (s2 *ManagementStrategy) Set() (*pkg.ModuleState, error) { return s2.state, nil // noop } -func (s2 *ManagementStrategy) Get() (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) Get() (*optic.ModuleState, error) { _, err := s2.GetAdministrativeInformation() if err != nil { return nil, err @@ -80,7 +81,7 @@ func (s2 *ManagementStrategy) Get() (*pkg.ModuleState, error) { return s2.state, nil } -func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) GetAdministrativeInformation() (*optic.ModuleState, error) { bin, err := s2.state.GetPageBin(0x00, 0) if err != nil { return nil, err @@ -97,23 +98,23 @@ func (s2 *ManagementStrategy) GetAdministrativeInformation() (*pkg.ModuleState, s2.state.SFF8636.LowPwrOverride = bin[0x5D]&LowPwrOverrideMask == LowPwrOverrideMask // page 0x00 - s2.state.VendorName = util.ParseASCIIToString(bin[0x94:0xA3]) - s2.state.VendorPartNumber = util.ParseASCIIToString(bin[0xA8:0xB7]) - s2.state.VendorPartRevision = util.ParseASCIIToString(bin[0xA8:0xB7]) - s2.state.VendorSerialNumber = util.ParseASCIIToString(bin[0xC4:0xD3]) + s2.state.VendorName = util2.ParseASCIIToString(bin[0x94:0xA3]) + s2.state.VendorPartNumber = util2.ParseASCIIToString(bin[0xA8:0xB7]) + s2.state.VendorPartRevision = util2.ParseASCIIToString(bin[0xA8:0xB7]) + s2.state.VendorSerialNumber = util2.ParseASCIIToString(bin[0xC4:0xD3]) return s2.state, nil } -func (s2 *ManagementStrategy) SetAdministrativeInformation() (*pkg.ModuleState, error) { +func (s2 *ManagementStrategy) SetAdministrativeInformation() (*optic.ModuleState, error) { bin, err := s2.state.GetPageBin(0x00, 0) if err != nil { return nil, err } bin[0x5D] &^= LowPwrRequestSWMask | SoftwareResetMask // clear - bin[0x5D] |= LowPwrRequestSWMask & util.YesNoByte(s2.state.LowPwrRequestSW) - bin[0x5D] |= SoftwareResetMask & util.YesNoByte(s2.state.SoftwareReset) + bin[0x5D] |= LowPwrRequestSWMask & util2.YesNoByte(s2.state.LowPwrRequestSW) + bin[0x5D] |= SoftwareResetMask & util2.YesNoByte(s2.state.SoftwareReset) err = s2.state.WritePageBin(0x00, 0, bin) if err != nil { diff --git a/internal/pkg/optic/util/ascii.go b/internal/optic/util/ascii.go similarity index 100% rename from internal/pkg/optic/util/ascii.go rename to internal/optic/util/ascii.go diff --git a/internal/pkg/optic/util/bin.go b/internal/optic/util/bin.go similarity index 100% rename from internal/pkg/optic/util/bin.go rename to internal/optic/util/bin.go diff --git a/internal/pkg/optic/util/bin_test.go b/internal/optic/util/bin_test.go similarity index 100% rename from internal/pkg/optic/util/bin_test.go rename to internal/optic/util/bin_test.go diff --git a/internal/pkg/optic/util/slice.go b/internal/optic/util/slice.go similarity index 100% rename from internal/pkg/optic/util/slice.go rename to internal/optic/util/slice.go diff --git a/internal/pkg/optic/cmis/common.go b/internal/pkg/optic/cmis/common.go deleted file mode 100644 index f8c28d8..0000000 --- a/internal/pkg/optic/cmis/common.go +++ /dev/null @@ -1,193 +0,0 @@ -package cmis - -import ( - "slices" - - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/optic/util" -) - -func checkSFF8024(sff8024Identifier byte, sff8024Revision byte) bool { - var CmisCompatibleSFF8024IDs = [...]byte{ - 0x1E, // qsfp+ or later with cmis - 0x1F, // sfp-dd with cmis - 0x20, // sfp+ or later with cmis - 0x21, // osfp-xd with cmis - 0x22, // oif-elfs with cmis - 0x23, // 4 lanes cdfp with cmis - 0x24, // 8 lanes cdfp with cmis - 0x25, // 16 lanes cdfp with cmis - 0x18, // qsfp-dd 8x - may support cmis - } - - const MajVerMask byte = 0xF0 // upper nibble is bits 7-4 per cmis rev 5.38 section 8.2.1 - const MinMajVer byte = 0x50 // minimum supported major version is 5 - - compatibleIdentifier := func(id byte) bool { return slices.Contains(CmisCompatibleSFF8024IDs[:], id) } - compatibleMajorRev := func(sff8024rev byte) bool { return (sff8024rev & MajVerMask) <= MinMajVer } - - // panic if CMIS and revision is above our maximum - if compatibleIdentifier(sff8024Identifier) && !compatibleMajorRev(sff8024Revision) { - panic(RefusalMajorTooHigh) - } - - // tells if compatible - return compatibleIdentifier(sff8024Identifier) -} - -// GetTunableLaserControlStatus maxN between 0 and 7 (channel N, used for only-channel-0 access) -func GetTunableLaserControlStatus( - state *pkg.ModuleState, - caps *pkg.CMISBankedTunableLaserControlAndStatus, - bank byte, - maxN int, -) (*pkg.ModuleState, error) { - dumpBin, err := state.GetPageBin(TunableLaserControlStatusPage, bank) - if err != nil { - return nil, err - } - - for i := 0; i <= maxN; i += 1 { - caps.GridSpacingTx[i] = dumpBin[0x80+i] & pkg.CMISGridSpacingTxMask - caps.GridSpacingTxROGhz[i] = pkg.CMISGridSpacingToFloatGhzMap[caps.GridSpacingTx[i]] - caps.FineTuningEnableTx[i] = dumpBin[0x80+i]&pkg.CMISFineTuningEnableTxMask != 0 - - caps.ChannelNumberTx[i] = util.ReadBeInt16(dumpBin, 0x88+byte(2*i)) // S16 over 2 bytes - caps.FineTuningOffsetMhzTx[i] = util.ReadBeInt16(dumpBin, 0x98+byte(2*i)) - caps.CurrentLaserFrequencyMhzTx[i] = util.ReadBeUint32(dumpBin, 0xA8+byte(4*i)) // U32 over 4 bytes, units Mhz - - caps.TargetOutputPowerTx[i] = util.ReadBeInt16(dumpBin, 0xC8+byte(2*i)) - - caps.TuningInProgressTx[i] = dumpBin[0xDE+i]&pkg.CMISTuningInProgressTxMask != 0 - caps.WaveLengthUnlockStatus[i] = dumpBin[0xDE+i]&pkg.CMISWavelengthUnlockStatusTxMask != 0 - - // per spec: the bit n-1 is set if and only if any of the latched flags are set to 1 - caps.LaserTuningFlagSummaryTx[i] = (dumpBin[0xE6+i] & (0b0000_0001 << i)) != 0 - - caps.TargetOutputPowerOORFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISTargetOutputPowerOORFlagTxMask != 0 - caps.FineTuningOutOfRangeFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISFineTuningOutOfRangeFlagTxMask != 0 - caps.TuningNotAcceptedMaskTx[i] = dumpBin[0xE7+i]&pkg.CMISTuningNotAcceptedFlagTxMask != 0 - caps.InvalidChannelNumberFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISInvalidChannelNumberFlagTxMask != 0 - caps.WavelengthUnlockedFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISWavelengthUnlockedFlagTxMask != 0 - caps.TuningCompleteFlagTx[i] = dumpBin[0xE7+i]&pkg.CMISTuningCompleteFlagTxMask != 0 - - // reading interrupt masks, bitfield pattern same as alarm - caps.TargetOutputPowerOORMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISTargetOutputPowerOORFlagTxMask != 0 - caps.FineTuningPowerOutOfRangeMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISFineTuningOutOfRangeFlagTxMask != 0 - caps.TuningNotAcceptedMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISTuningNotAcceptedFlagTxMask != 0 - caps.InvalidChannelMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISInvalidChannelNumberFlagTxMask != 0 - caps.WavelengthUnlockedMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISWavelengthUnlockedFlagTxMask != 0 - caps.TuningCompleteMaskTx[i] = dumpBin[0xEF+i]&pkg.CMISTuningCompleteFlagTxMask != 0 - } - - return state, nil -} - -func SetTunableLaserControlStatus( - state *pkg.ModuleState, - caps *pkg.CMISBankedTunableLaserControlAndStatus, - bank byte, - maxN int, -) (*pkg.ModuleState, error) { - dumpBin, err := state.GetPageBin(TunableLaserControlStatusPage, bank) - if err != nil { - return nil, err - } - - for i := 0; i <= maxN; i += 1 { - // rewrite to mem - dumpBin[0x80+i] = (caps.GridSpacingTx[i] & pkg.CMISGridSpacingTxMask) | - (util.YesNoByte(caps.FineTuningEnableTx[i]) & pkg.CMISFineTuningEnableTxMask) - - util.WriteBeInt16(caps.ChannelNumberTx[i], dumpBin, 0x88+byte(2*i)) - util.WriteBeInt16(caps.FineTuningOffsetMhzTx[i], dumpBin, 0x98+byte(2*i)) - // no write for CurrentLaserFrequency, read-only - - util.WriteBeInt16(caps.TargetOutputPowerTx[i], dumpBin, 0xC8+byte(2*i)) - // subsequent fields read-only - - dumpBin[0xEF+i] = (util.YesNoByte(caps.TargetOutputPowerOORMaskTx[i]) & pkg.CMISTargetOutputPowerOORFlagTxMask) | - (util.YesNoByte(caps.FineTuningPowerOutOfRangeMaskTx[i]) & pkg.CMISFineTuningOutOfRangeFlagTxMask) | - (util.YesNoByte(caps.TuningNotAcceptedMaskTx[i]) & pkg.CMISTuningNotAcceptedFlagTxMask) | - (util.YesNoByte(caps.InvalidChannelMaskTx[i]) & pkg.CMISInvalidChannelNumberFlagTxMask) | - (util.YesNoByte(caps.WavelengthUnlockedMaskTx[i]) & pkg.CMISWavelengthUnlockedFlagTxMask) | - (util.YesNoByte(caps.TuningCompleteMaskTx[i]) & pkg.CMISTuningCompleteFlagTxMask) - } - - err = state.WritePageBin(TunableLaserControlStatusPage, bank, dumpBin) - if err != nil { - return nil, err - } - - return state, nil -} - -func GetLaserCapabilitiesAdvertising( - state *pkg.ModuleState, - caps *pkg.CMISLaserCapabilitiesAdvertising, -) (*pkg.ModuleState, error) { - dumpBin, err := state.GetPageBin(0x04, 0x00) // non-banked - if err != nil { - return nil, err - } - - caps.SupportedGridSpacings = make(map[string]bool) - - // I really hate that Go doesn't have bitfields. - caps.SupportedGridSpacings[pkg.Grid75string] = dumpBin[0x80]&GridSupported75GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid33String] = dumpBin[0x80]&GridSupported33GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid100String] = dumpBin[0x80]&GridSupported100GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid50String] = dumpBin[0x80]&GridSupported50GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid25String] = dumpBin[0x80]&GridSupported25GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid12p5String] = dumpBin[0x80]&GridSupported12p5GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid6p250String] = dumpBin[0x80]&GridSupported6p25GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid3p125String] = dumpBin[0x80]&GridSupported3p125GhzMask != 0 - caps.SupportedGridSpacings[pkg.Grid150String] = dumpBin[0x81]&GridSupported150GhzMask != 0 - - caps.FineTuningSupported = dumpBin[0x81]&FineTuningSupportedMask != 0 - - caps.GridLowChannel = make(map[string]int16) - caps.GridHighChannel = make(map[string]int16) - - var base byte = 0x82 - // 3.125Ghz - caps.GridLowChannel[pkg.Grid3p125String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid3p125String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 6.25Ghz - caps.GridLowChannel[pkg.Grid6p250String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid6p250String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 12.5Ghz - caps.GridLowChannel[pkg.Grid12p5String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid12p5String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 25Ghz - caps.GridLowChannel[pkg.Grid25String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid25String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 50Ghz - caps.GridLowChannel[pkg.Grid50String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid50String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 100Ghz - caps.GridLowChannel[pkg.Grid100String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid100String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 33Ghz - caps.GridLowChannel[pkg.Grid33String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid33String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 75Ghz - caps.GridLowChannel[pkg.Grid75string] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid75string] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - // 150Ghz - caps.GridLowChannel[pkg.Grid150String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.GridHighChannel[pkg.Grid150String] = util.ReadBeInt16AndShiftBase(dumpBin, &base) - - base = 0xBE // skip reserved region - caps.FineTuningResolution = util.ReadBeUint16AndShiftBase(dumpBin, &base) - caps.FineTuningLowOffset = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.FineTuningHighOffset = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.ProgOutputPowerPerLaneSupported = dumpBin[base]&ProgOutputPowerPerLaneSupported != 0 - - base = 0xC6 // skip reserved region - caps.ProgOutputPowerMin = util.ReadBeInt16AndShiftBase(dumpBin, &base) - caps.ProgOutputPowerMax = util.ReadBeInt16AndShiftBase(dumpBin, &base) - - // TODO checksum support - return state, nil -} diff --git a/internal/pkg/rtbrick/rtbrick.go b/internal/rtbrick/rtbrick.go similarity index 100% rename from internal/pkg/rtbrick/rtbrick.go rename to internal/rtbrick/rtbrick.go diff --git a/internal/pkg/rtbrick/ssh/connection.go b/internal/rtbrick/ssh/connection.go similarity index 98% rename from internal/pkg/rtbrick/ssh/connection.go rename to internal/rtbrick/ssh/connection.go index a7ed1b9..4950fea 100644 --- a/internal/pkg/rtbrick/ssh/connection.go +++ b/internal/rtbrick/ssh/connection.go @@ -11,7 +11,7 @@ import ( "github.com/pkg/errors" "github.com/pkg/sftp" - "github.com/wobcom/rtbrick-optic-programmer/internal/pkg/rtbrick" + "github.com/wobcom/rtbrick-optic-programmer/internal/rtbrick" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" "gopkg.in/yaml.v3" diff --git a/internal/pkg/rtbrick/ssh/i2c_handle.go b/internal/rtbrick/ssh/i2c_handle.go similarity index 100% rename from internal/pkg/rtbrick/ssh/i2c_handle.go rename to internal/rtbrick/ssh/i2c_handle.go diff --git a/internal/pkg/util.go b/internal/util.go similarity index 97% rename from internal/pkg/util.go rename to internal/util.go index a0e4a15..1b7449a 100644 --- a/internal/pkg/util.go +++ b/internal/util.go @@ -1,4 +1,4 @@ -package pkg +package internal import ( "bytes" From a1784a241bf393505310a10f0dd8ab73122b24f2 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 1 Jul 2026 12:13:28 +0200 Subject: [PATCH 34/36] add build workflow --- .github/workflows/build.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c59f2e1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,20 @@ +name: "build" +on: + push: +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v6 + with: + go-version-file: 'go.mod' + cache-dependency-path: 'go.sum' + - name: build cmd + run: + go build ./cmd/optic-programmer.go + - uses: actions/upload-artifact@v4 + with: + name: optic-programmer + path: optic-programmer + From 0708649b5c06e54caaf8351012ab30107e74fafa Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 1 Jul 2026 12:26:25 +0200 Subject: [PATCH 35/36] add test workflow --- .github/workflows/test.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..c6b1dc2 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,15 @@ +name: "test" +on: + push: + pull_request: +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v6 + with: + go-version-file: 'go.mod' + cache-dependency-path: 'go.sum' + - name: test internal + run: 'go test ./internal/...' From 3b9adb8869e093031a9775d96e57abdd2f284467 Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 1 Jul 2026 13:23:55 +0200 Subject: [PATCH 36/36] re-implement high power override for sff8636 --- cmd/optic-programmer.go | 9 +++++++++ internal/optic/sff8636/sff8636.go | 9 ++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/cmd/optic-programmer.go b/cmd/optic-programmer.go index ef00f20..53ed573 100644 --- a/cmd/optic-programmer.go +++ b/cmd/optic-programmer.go @@ -223,8 +223,17 @@ func main() { if toggle == "on" { module.LowPwrRequestSW = true + // software set enabled for sff8636 (overrides LPMode pad) + module.SFF8636.LowPwrOverride = true + // SFF8636 software override, disable high power classes + module.SFF8636.EnableHighPowerClass57 = false + module.SFF8636.EnableHighPowerClass8 = false } else if toggle == "off" { module.LowPwrRequestSW = false + module.SFF8636.LowPwrOverride = true + // SFF8636 software override, enable high power classes + module.SFF8636.EnableHighPowerClass57 = true + module.SFF8636.EnableHighPowerClass8 = true } else { return errors.New(fmt.Sprintf("cannot parse on/off argument %s", toggle)) } diff --git a/internal/optic/sff8636/sff8636.go b/internal/optic/sff8636/sff8636.go index 68676bb..54f8122 100644 --- a/internal/optic/sff8636/sff8636.go +++ b/internal/optic/sff8636/sff8636.go @@ -112,9 +112,16 @@ func (s2 *ManagementStrategy) SetAdministrativeInformation() (*optic.ModuleState return nil, err } - bin[0x5D] &^= LowPwrRequestSWMask | SoftwareResetMask // clear + bin[0x5D] &^= LowPwrRequestSWMask | + SoftwareResetMask | + EnableHighPowerClass57Mask | + EnableHighPowerClass8Mask | + LowPwrOverrideMask // clear bin[0x5D] |= LowPwrRequestSWMask & util2.YesNoByte(s2.state.LowPwrRequestSW) bin[0x5D] |= SoftwareResetMask & util2.YesNoByte(s2.state.SoftwareReset) + bin[0x5D] |= LowPwrOverrideMask & util2.YesNoByte(s2.state.SFF8636.LowPwrOverride) + bin[0x5D] |= EnableHighPowerClass57Mask & util2.YesNoByte(s2.state.SFF8636.EnableHighPowerClass57) + bin[0x5D] |= EnableHighPowerClass8Mask & util2.YesNoByte(s2.state.SFF8636.EnableHighPowerClass8) err = s2.state.WritePageBin(0x00, 0, bin) if err != nil {