From b10eb8fe5cf6ad397bcebe30a53d8607d9191b09 Mon Sep 17 00:00:00 2001 From: Patryk Strusiewicz-Surmacki Date: Thu, 14 Oct 2021 12:53:57 +0100 Subject: [PATCH] Support for devlink info command --- devlink_linux.go | 215 ++++++++++++++++++++++++++++++++++++++++++++ devlink_test.go | 112 +++++++++++++++++++++++ nl/devlink_linux.go | 8 ++ 3 files changed, 335 insertions(+) diff --git a/devlink_linux.go b/devlink_linux.go index c7eceaa..358b232 100644 --- a/devlink_linux.go +++ b/devlink_linux.go @@ -3,6 +3,7 @@ package netlink import ( "fmt" "net" + "strings" "syscall" "github.com/vishvananda/netlink/nl" @@ -65,6 +66,24 @@ type DevLinkPortAddAttrs struct { ControllerValid bool } +// DevlinkDeviceInfo represents devlink info +type DevlinkDeviceInfo struct { + Driver string + SerialNumber string + BoardID string + FwApp string + FwAppBoundleID string + FwAppName string + FwBoundleID string + FwMgmt string + FwMgmtAPI string + FwMgmtBuild string + FwNetlist string + FwNetlistBuild string + FwPsidAPI string + FwUndi string +} + func parseDevLinkDeviceList(msgs [][]byte) ([]*DevlinkDevice, error) { devices := make([]*DevlinkDevice, 0, len(msgs)) for _, m := range msgs { @@ -511,3 +530,199 @@ func (h *Handle) DevlinkPortFnSet(Bus string, Device string, PortIndex uint32, F func DevlinkPortFnSet(Bus string, Device string, PortIndex uint32, FnAttrs DevlinkPortFnSetAttrs) error { return pkgHandle.DevlinkPortFnSet(Bus, Device, PortIndex, FnAttrs) } + +// devlinkInfoGetter is function that is responsible for getting devlink info message +// this is introduced for test purpose +type devlinkInfoGetter func(bus, device string) ([]byte, error) + +// DevlinkGetDeviceInfoByName returns devlink info for selected device, +// otherwise returns an error code. +// Equivalent to: `devlink dev info $dev` +func (h *Handle) DevlinkGetDeviceInfoByName(Bus string, Device string, getInfoMsg devlinkInfoGetter) (*DevlinkDeviceInfo, error) { + info, err := h.DevlinkGetDeviceInfoByNameAsMap(Bus, Device, getInfoMsg) + if err != nil { + return nil, err + } + + return parseInfoData(info), nil +} + +// DevlinkGetDeviceInfoByName returns devlink info for selected device, +// otherwise returns an error code. +// Equivalent to: `devlink dev info $dev` +func DevlinkGetDeviceInfoByName(Bus string, Device string) (*DevlinkDeviceInfo, error) { + return pkgHandle.DevlinkGetDeviceInfoByName(Bus, Device, pkgHandle.getDevlinkInfoMsg) +} + +// DevlinkGetDeviceInfoByNameAsMap returns devlink info for selected device as a map, +// otherwise returns an error code. +// Equivalent to: `devlink dev info $dev` +func (h *Handle) DevlinkGetDeviceInfoByNameAsMap(Bus string, Device string, getInfoMsg devlinkInfoGetter) (map[string]string, error) { + response, err := getInfoMsg(Bus, Device) + if err != nil { + return nil, err + } + + info, err := parseInfoMsg(response) + if err != nil { + return nil, err + } + + return info, nil +} + +// DevlinkGetDeviceInfoByNameAsMap returns devlink info for selected device as a map, +// otherwise returns an error code. +// Equivalent to: `devlink dev info $dev` +func DevlinkGetDeviceInfoByNameAsMap(Bus string, Device string) (map[string]string, error) { + return pkgHandle.DevlinkGetDeviceInfoByNameAsMap(Bus, Device, pkgHandle.getDevlinkInfoMsg) +} + +// GetDevlinkInfo returns devlink info for target device, +// otherwise returns an error code. +func (d *DevlinkDevice) GetDevlinkInfo() (*DevlinkDeviceInfo, error) { + return pkgHandle.DevlinkGetDeviceInfoByName(d.BusName, d.DeviceName, pkgHandle.getDevlinkInfoMsg) +} + +// GetDevlinkInfoAsMap returns devlink info for target device as a map, +// otherwise returns an error code. +func (d *DevlinkDevice) GetDevlinkInfoAsMap() (map[string]string, error) { + return pkgHandle.DevlinkGetDeviceInfoByNameAsMap(d.BusName, d.DeviceName, pkgHandle.getDevlinkInfoMsg) +} + +func (h *Handle) getDevlinkInfoMsg(bus, device string) ([]byte, error) { + _, req, err := h.createCmdReq(nl.DEVLINK_CMD_INFO_GET, bus, device) + if err != nil { + return nil, err + } + + response, err := req.Execute(unix.NETLINK_GENERIC, 0) + if err != nil { + return nil, err + } + + if len(response) < 1 { + return nil, fmt.Errorf("getDevlinkInfoMsg: message too short") + } + + return response[0], nil +} + +func parseInfoMsg(msg []byte) (map[string]string, error) { + if len(msg) < nl.SizeofGenlmsg { + return nil, fmt.Errorf("parseInfoMsg: message too short") + } + + info := make(map[string]string) + err := collectInfoData(msg[nl.SizeofGenlmsg:], info) + + if err != nil { + return nil, err + } + + return info, nil +} + +func collectInfoData(msg []byte, data map[string]string) error { + attrs, err := nl.ParseRouteAttr(msg) + if err != nil { + return err + } + + for _, attr := range attrs { + switch attr.Attr.Type { + case nl.DEVLINK_ATTR_INFO_DRIVER_NAME: + data["driver"] = parseInfoValue(attr.Value) + case nl.DEVLINK_ATTR_INFO_SERIAL_NUMBER: + data["serialNumber"] = parseInfoValue(attr.Value) + case nl.DEVLINK_ATTR_INFO_VERSION_RUNNING, nl.DEVLINK_ATTR_INFO_VERSION_FIXED, + nl.DEVLINK_ATTR_INFO_VERSION_STORED: + key, value, err := getNestedInfoData(attr.Value) + if err != nil { + return err + } + data[key] = value + } + } + + if len(data) == 0 { + return fmt.Errorf("collectInfoData: could not read attributes") + } + + return nil +} + +func getNestedInfoData(msg []byte) (string, string, error) { + nestedAttrs, err := nl.ParseRouteAttr(msg) + + var key, value string + + if err != nil { + return "", "", err + } + + if len(nestedAttrs) != 2 { + return "", "", fmt.Errorf("getNestedInfoData: too few attributes in nested structure") + } + + for _, nestedAttr := range nestedAttrs { + switch nestedAttr.Attr.Type { + case nl.DEVLINK_ATTR_INFO_VERSION_NAME: + key = parseInfoValue(nestedAttr.Value) + case nl.DEVLINK_ATTR_INFO_VERSION_VALUE: + value = parseInfoValue(nestedAttr.Value) + } + } + + if key == "" { + return "", "", fmt.Errorf("getNestedInfoData: key not found") + } + + if value == "" { + return "", "", fmt.Errorf("getNestedInfoData: value not found") + } + + return key, value, nil +} + +func parseInfoData(data map[string]string) *DevlinkDeviceInfo { + info := new(DevlinkDeviceInfo) + for key, value := range data { + switch key { + case "driver": + info.Driver = value + case "serialNumber": + info.SerialNumber = value + case "board.id": + info.BoardID = value + case "fw.app": + info.FwApp = value + case "fw.app.bundle_id": + info.FwAppBoundleID = value + case "fw.app.name": + info.FwAppName = value + case "fw.bundle_id": + info.FwBoundleID = value + case "fw.mgmt": + info.FwMgmt = value + case "fw.mgmt.api": + info.FwMgmtAPI = value + case "fw.mgmt.build": + info.FwMgmtBuild = value + case "fw.netlist": + info.FwNetlist = value + case "fw.netlist.build": + info.FwNetlistBuild = value + case "fw.psid.api": + info.FwPsidAPI = value + case "fw.undi": + info.FwUndi = value + } + } + return info +} + +func parseInfoValue(value []byte) string { + v := strings.ReplaceAll(string(value), "\x00", "") + return strings.TrimSpace(v) +} diff --git a/devlink_test.go b/devlink_test.go index b1f7e5e..fdd9382 100644 --- a/devlink_test.go +++ b/devlink_test.go @@ -150,3 +150,115 @@ func init() { flag.StringVar(&device, "device", "", "devlink device devicename") flag.UintVar(&sfnum, "sfnum", 0, "devlink port sfnumber") } + +func TestDevlinkGetDeviceInfoByNameAsMap(t *testing.T) { + info, err := pkgHandle.DevlinkGetDeviceInfoByNameAsMap("pci", "0000:00:00.0", mockDevlinkInfoGetter) + if err != nil { + t.Fatal(err) + } + testInfo := devlinkTestInfoParesd() + for k, v := range info { + if testInfo[k] != v { + t.Fatal("Value", v, "retrieved for key", k, "is not equal to", testInfo[k]) + } + } +} + +func TestDevlinkGetDeviceInfoByName(t *testing.T) { + info, err := pkgHandle.DevlinkGetDeviceInfoByName("pci", "0000:00:00.0", mockDevlinkInfoGetter) + if err != nil { + t.Fatal(err) + } + testInfo := parseInfoData(devlinkTestInfoParesd()) + if !areInfoStructsEqual(info, testInfo) { + t.Fatal("Info structures are not equal") + } +} + +func TestDevlinkGetDeviceInfoByNameAsMapFail(t *testing.T) { + _, err := pkgHandle.DevlinkGetDeviceInfoByNameAsMap("pci", "0000:00:00.0", mockDevlinkInfoGetterEmpty) + if err == nil { + t.Fatal() + } +} + +func TestDevlinkGetDeviceInfoByNameFail(t *testing.T) { + _, err := pkgHandle.DevlinkGetDeviceInfoByName("pci", "0000:00:00.0", mockDevlinkInfoGetterEmpty) + if err == nil { + t.Fatal() + } +} + +func mockDevlinkInfoGetter(bus, device string) ([]byte, error) { + return devlinkInfo(), nil +} + +func mockDevlinkInfoGetterEmpty(bus, device string) ([]byte, error) { + return []byte{}, nil +} + +func devlinkInfo() []byte { + return []byte{51, 1, 0, 0, 8, 0, 1, 0, 112, 99, 105, 0, 17, 0, 2, 0, 48, + 48, 48, 48, 58, 56, 52, 58, 48, 48, 46, 48, 0, 0, 0, 0, 8, 0, 98, 0, + 105, 99, 101, 0, 28, 0, 99, 0, 51, 48, 45, 56, 57, 45, 97, 51, 45, + 102, 102, 45, 102, 102, 45, 99, 97, 45, 48, 53, 45, 54, 56, 0, 36, + 0, 100, 0, 13, 0, 103, 0, 98, 111, 97, 114, 100, 46, 105, 100, 0, 0, + 0, 0, 15, 0, 104, 0, 75, 56, 53, 53, 56, 53, 45, 48, 48, 48, 0, 0, + 28, 0, 101, 0, 12, 0, 103, 0, 102, 119, 46, 109, 103, 109, 116, 0, + 10, 0, 104, 0, 53, 46, 52, 46, 53, 0, 0, 0, 28, 0, 101, 0, 16, 0, + 103, 0, 102, 119, 46, 109, 103, 109, 116, 46, 97, 112, 105, 0, 8, 0, + 104, 0, 49, 46, 55, 0, 40, 0, 101, 0, 18, 0, 103, 0, 102, 119, 46, + 109, 103, 109, 116, 46, 98, 117, 105, 108, 100, 0, 0, 0, 15, 0, 104, + 0, 48, 120, 51, 57, 49, 102, 55, 54, 52, 48, 0, 0, 32, 0, 101, 0, + 12, 0, 103, 0, 102, 119, 46, 117, 110, 100, 105, 0, 13, 0, 104, 0, + 49, 46, 50, 56, 57, 56, 46, 48, 0, 0, 0, 0, 32, 0, 101, 0, 16, 0, + 103, 0, 102, 119, 46, 112, 115, 105, 100, 46, 97, 112, 105, 0, 9, 0, + 104, 0, 50, 46, 52, 50, 0, 0, 0, 0, 40, 0, 101, 0, 17, 0, 103, 0, + 102, 119, 46, 98, 117, 110, 100, 108, 101, 95, 105, 100, 0, 0, 0, 0, + 15, 0, 104, 0, 48, 120, 56, 48, 48, 48, 55, 48, 54, 98, 0, 0, 48, 0, + 101, 0, 16, 0, 103, 0, 102, 119, 46, 97, 112, 112, 46, 110, 97, 109, + 101, 0, 27, 0, 104, 0, 73, 67, 69, 32, 79, 83, 32, 68, 101, 102, 97, + 117, 108, 116, 32, 80, 97, 99, 107, 97, 103, 101, 0, 0, 32, 0, 101, + 0, 11, 0, 103, 0, 102, 119, 46, 97, 112, 112, 0, 0, 13, 0, 104, 0, + 49, 46, 51, 46, 50, 52, 46, 48, 0, 0, 0, 0, 44, 0, 101, 0, 21, 0, + 103, 0, 102, 119, 46, 97, 112, 112, 46, 98, 117, 110, 100, 108, + 101, 95, 105, 100, 0, 0, 0, 0, 15, 0, 104, 0, 48, 120, 99, 48, 48, + 48, 48, 48, 48, 49, 0, 0, 44, 0, 101, 0, 15, 0, 103, 0, 102, 119, + 46, 110, 101, 116, 108, 105, 115, 116, 0, 0, 21, 0, 104, 0, 50, 46, + 52, 48, 46, 50, 48, 48, 48, 45, 51, 46, 49, 54, 46, 48, 0, 0, 0, 0, + 44, 0, 101, 0, 21, 0, 103, 0, 102, 119, 46, 110, 101, 116, 108, 105, + 115, 116, 46, 98, 117, 105, 108, 100, 0, 0, 0, 0, 15, 0, 104, 0, 48, + 120, 54, 55, 54, 97, 52, 56, 57, 100, 0, 0} +} + +func devlinkTestInfoParesd() map[string]string { + return map[string]string{ + "board.id": "K85585-000", + "fw.app": "1.3.24.0", + "fw.app.bundle_id": "0xc0000001", + "fw.app.name": "ICE OS Default Package", + "fw.bundle_id": "0x8000706b", + "fw.mgmt": "5.4.5", + "fw.mgmt.api": "1.7", + "fw.mgmt.build": "0x391f7640", + "fw.netlist": "2.40.2000-3.16.0", + "fw.netlist.build": "0x676a489d", + "fw.psid.api": "2.42", + "fw.undi": "1.2898.0", + "driver": "ice", + "serialNumber": "30-89-a3-ff-ff-ca-05-68", + } +} + +func areInfoStructsEqual(first *DevlinkDeviceInfo, second *DevlinkDeviceInfo) bool { + if first.FwApp != second.FwApp || first.FwAppBoundleID != second.FwAppBoundleID || + first.FwAppName != second.FwAppName || first.FwBoundleID != second.FwBoundleID || + first.FwMgmt != second.FwMgmt || first.FwMgmtAPI != second.FwMgmtAPI || + first.FwMgmtBuild != second.FwMgmtBuild || first.FwNetlist != second.FwNetlist || + first.FwNetlistBuild != second.FwNetlistBuild || first.FwPsidAPI != second.FwPsidAPI || + first.BoardID != second.BoardID || first.FwUndi != second.FwUndi || + first.Driver != second.Driver || first.SerialNumber != second.SerialNumber { + return false + } + return true +} diff --git a/nl/devlink_linux.go b/nl/devlink_linux.go index 2410922..2995da4 100644 --- a/nl/devlink_linux.go +++ b/nl/devlink_linux.go @@ -16,6 +16,7 @@ const ( DEVLINK_CMD_PORT_DEL = 8 DEVLINK_CMD_ESWITCH_GET = 29 DEVLINK_CMD_ESWITCH_SET = 30 + DEVLINK_CMD_INFO_GET = 51 ) const ( @@ -30,6 +31,13 @@ const ( DEVLINK_ATTR_ESWITCH_INLINE_MODE = 26 DEVLINK_ATTR_ESWITCH_ENCAP_MODE = 62 DEVLINK_ATTR_PORT_FLAVOUR = 77 + DEVLINK_ATTR_INFO_DRIVER_NAME = 98 + DEVLINK_ATTR_INFO_SERIAL_NUMBER = 99 + DEVLINK_ATTR_INFO_VERSION_FIXED = 100 + DEVLINK_ATTR_INFO_VERSION_RUNNING = 101 + DEVLINK_ATTR_INFO_VERSION_STORED = 102 + DEVLINK_ATTR_INFO_VERSION_NAME = 103 + DEVLINK_ATTR_INFO_VERSION_VALUE = 104 DEVLINK_ATTR_PORT_PCI_PF_NUMBER = 127 DEVLINK_ATTR_PORT_FUNCTION = 145 DEVLINK_ATTR_PORT_CONTROLLER_NUMBER = 150