Skip to content

Commit

Permalink
Add functions to work with devlink device parameters
Browse files Browse the repository at this point in the history
Functions added:

DevlinkGetDeviceParams - get all parameters for device
DevlinkGetDeviceParamByName - get specific parameter for device
DevlinkSetDeviceParam - set parameter for device

Signed-off-by: Yury Kulazhenkov <ykulazhenkov@nvidia.com>
  • Loading branch information
ykulazhenkov authored and aboch committed Feb 12, 2024
1 parent 857968a commit 5daafaf
Show file tree
Hide file tree
Showing 3 changed files with 382 additions and 0 deletions.
239 changes: 239 additions & 0 deletions devlink_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,107 @@ func (dlrs *DevlinkResources) parseAttributes(attrs map[uint16]syscall.NetlinkRo
return nil
}

// DevlinkParam represents parameter of the device
type DevlinkParam struct {
Name string
IsGeneric bool
Type uint8 // possible values are in nl.DEVLINK_PARAM_TYPE_* constants
Values []DevlinkParamValue
}

// DevlinkParamValue contains values of the parameter
// Data field contains specific type which can be casted by unsing info from the DevlinkParam.Type field
type DevlinkParamValue struct {
rawData []byte
Data interface{}
CMODE uint8 // possible values are in nl.DEVLINK_PARAM_CMODE_* constants
}

// parseAttributes parses provided Netlink Attributes and populates DevlinkParam, returns error if occured
func (dlp *DevlinkParam) parseAttributes(attrs []syscall.NetlinkRouteAttr) error {
var valuesList [][]syscall.NetlinkRouteAttr
for _, attr := range attrs {
switch attr.Attr.Type {
case nl.DEVLINK_ATTR_PARAM:
nattrs, err := nl.ParseRouteAttr(attr.Value)
if err != nil {
return err
}
for _, nattr := range nattrs {
switch nattr.Attr.Type {
case nl.DEVLINK_ATTR_PARAM_NAME:
dlp.Name = nl.BytesToString(nattr.Value)
case nl.DEVLINK_ATTR_PARAM_GENERIC:
dlp.IsGeneric = true
case nl.DEVLINK_ATTR_PARAM_TYPE:
if len(nattr.Value) == 1 {
dlp.Type = nattr.Value[0]
}
case nl.DEVLINK_ATTR_PARAM_VALUES_LIST:
nnattrs, err := nl.ParseRouteAttr(nattr.Value)
if err != nil {
return err
}
valuesList = append(valuesList, nnattrs)
}
}
}
}
for _, valAttr := range valuesList {
v := DevlinkParamValue{}
if err := v.parseAttributes(valAttr, dlp.Type); err != nil {
return err
}
dlp.Values = append(dlp.Values, v)
}
return nil
}

func (dlpv *DevlinkParamValue) parseAttributes(attrs []syscall.NetlinkRouteAttr, paramType uint8) error {
for _, attr := range attrs {
nattrs, err := nl.ParseRouteAttr(attr.Value)
if err != nil {
return err
}
var rawData []byte
for _, nattr := range nattrs {
switch nattr.Attr.Type {
case nl.DEVLINK_ATTR_PARAM_VALUE_DATA:
rawData = nattr.Value
case nl.DEVLINK_ATTR_PARAM_VALUE_CMODE:
if len(nattr.Value) == 1 {
dlpv.CMODE = nattr.Value[0]
}
}
}
switch paramType {
case nl.DEVLINK_PARAM_TYPE_U8:
dlpv.Data = uint8(0)
if rawData != nil && len(rawData) == 1 {
dlpv.Data = uint8(rawData[0])
}
case nl.DEVLINK_PARAM_TYPE_U16:
dlpv.Data = uint16(0)
if rawData != nil {
dlpv.Data = native.Uint16(rawData)
}
case nl.DEVLINK_PARAM_TYPE_U32:
dlpv.Data = uint32(0)
if rawData != nil {
dlpv.Data = native.Uint32(rawData)
}
case nl.DEVLINK_PARAM_TYPE_STRING:
dlpv.Data = ""
if rawData != nil {
dlpv.Data = nl.BytesToString(rawData)
}
case nl.DEVLINK_PARAM_TYPE_BOOL:
dlpv.Data = rawData != nil
}
}
return nil
}

func parseDevLinkDeviceList(msgs [][]byte) ([]*DevlinkDevice, error) {
devices := make([]*DevlinkDevice, 0, len(msgs))
for _, m := range msgs {
Expand Down Expand Up @@ -635,6 +736,144 @@ func (h *Handle) DevlinkGetDeviceResources(bus string, device string) (*DevlinkR
return &resources, nil
}

// DevlinkGetDeviceParams returns parameters for devlink device
// Equivalent to: `devlink dev param show <bus>/<device>`
func (h *Handle) DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkParam, error) {
_, req, err := h.createCmdReq(nl.DEVLINK_CMD_PARAM_GET, bus, device)
if err != nil {
return nil, err
}
req.Flags |= unix.NLM_F_DUMP
respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0)
if err != nil {
return nil, err
}
var params []*DevlinkParam
for _, m := range respmsg {
attrs, err := nl.ParseRouteAttr(m[nl.SizeofGenlmsg:])
if err != nil {
return nil, err
}
p := &DevlinkParam{}
if err := p.parseAttributes(attrs); err != nil {
return nil, err
}
params = append(params, p)
}

return params, nil
}

// DevlinkGetDeviceParams returns parameters for devlink device
// Equivalent to: `devlink dev param show <bus>/<device>`
func DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkParam, error) {
return pkgHandle.DevlinkGetDeviceParams(bus, device)
}

// DevlinkGetDeviceParamByName returns specific parameter for devlink device
// Equivalent to: `devlink dev param show <bus>/<device> name <param>`
func (h *Handle) DevlinkGetDeviceParamByName(bus string, device string, param string) (*DevlinkParam, error) {
_, req, err := h.createCmdReq(nl.DEVLINK_CMD_PARAM_GET, bus, device)
if err != nil {
return nil, err
}
req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_NAME, nl.ZeroTerminated(param)))
respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0)
if err != nil {
return nil, err
}
if len(respmsg) == 0 {
return nil, fmt.Errorf("unexpected response")
}
attrs, err := nl.ParseRouteAttr(respmsg[0][nl.SizeofGenlmsg:])
if err != nil {
return nil, err
}
p := &DevlinkParam{}
if err := p.parseAttributes(attrs); err != nil {
return nil, err
}
return p, nil
}

// DevlinkGetDeviceParamByName returns specific parameter for devlink device
// Equivalent to: `devlink dev param show <bus>/<device> name <param>`
func DevlinkGetDeviceParamByName(bus string, device string, param string) (*DevlinkParam, error) {
return pkgHandle.DevlinkGetDeviceParamByName(bus, device, param)
}

// DevlinkSetDeviceParam set specific parameter for devlink device
// Equivalent to: `devlink dev param set <bus>/<device> name <param> cmode <cmode> value <value>`
// cmode argument should contain valid cmode value as uint8, modes are define in nl.DEVLINK_PARAM_CMODE_* constants
// value argument should have one of the following types: uint8, uint16, uint32, string, bool
func (h *Handle) DevlinkSetDeviceParam(bus string, device string, param string, cmode uint8, value interface{}) error {
// retrive the param type
p, err := h.DevlinkGetDeviceParamByName(bus, device, param)
if err != nil {
return fmt.Errorf("failed to get device param: %v", err)
}
paramType := p.Type

_, req, err := h.createCmdReq(nl.DEVLINK_CMD_PARAM_SET, bus, device)
if err != nil {
return err
}
req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_TYPE, nl.Uint8Attr(paramType)))
req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_NAME, nl.ZeroTerminated(param)))
req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_VALUE_CMODE, nl.Uint8Attr(cmode)))

var valueAsBytes []byte
switch paramType {
case nl.DEVLINK_PARAM_TYPE_U8:
v, ok := value.(uint8)
if !ok {
return fmt.Errorf("unepected value type required: uint8, actual: %T", value)
}
valueAsBytes = nl.Uint8Attr(v)
case nl.DEVLINK_PARAM_TYPE_U16:
v, ok := value.(uint16)
if !ok {
return fmt.Errorf("unepected value type required: uint16, actual: %T", value)
}
valueAsBytes = nl.Uint16Attr(v)
case nl.DEVLINK_PARAM_TYPE_U32:
v, ok := value.(uint32)
if !ok {
return fmt.Errorf("unepected value type required: uint32, actual: %T", value)
}
valueAsBytes = nl.Uint32Attr(v)
case nl.DEVLINK_PARAM_TYPE_STRING:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unepected value type required: string, actual: %T", value)
}
valueAsBytes = nl.ZeroTerminated(v)
case nl.DEVLINK_PARAM_TYPE_BOOL:
v, ok := value.(bool)
if !ok {
return fmt.Errorf("unepected value type required: bool, actual: %T", value)
}
if v {
valueAsBytes = []byte{}
}
default:
return fmt.Errorf("unsupported parameter type: %d", paramType)
}
if valueAsBytes != nil {
req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_VALUE_DATA, valueAsBytes))
}
_, err = req.Execute(unix.NETLINK_GENERIC, 0)
return err
}

// DevlinkSetDeviceParam set specific parameter for devlink device
// Equivalent to: `devlink dev param set <bus>/<device> name <param> cmode <cmode> value <value>`
// cmode argument should contain valid cmode value as uint8, modes are define in nl.DEVLINK_PARAM_CMODE_* constants
// value argument should have one of the following types: uint8, uint16, uint32, string, bool
func DevlinkSetDeviceParam(bus string, device string, param string, cmode uint8, value interface{}) error {
return pkgHandle.DevlinkSetDeviceParam(bus, device, param, cmode, value)
}

// DevLinkGetPortByIndex provides a pointer to devlink portand nil error,
// otherwise returns an error code.
func DevLinkGetPortByIndex(Bus string, Device string, PortIndex uint32) (*DevlinkPort, error) {
Expand Down
116 changes: 116 additions & 0 deletions devlink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ package netlink

import (
"flag"
"math/rand"
"net"
"os"
"strconv"
"testing"

"github.com/vishvananda/netlink/nl"
)

func TestDevLinkGetDeviceList(t *testing.T) {
Expand Down Expand Up @@ -286,3 +291,114 @@ func TestDevlinkGetDeviceResources(t *testing.T) {

t.Logf("Resources: %+v", res)
}

// devlink device parameters can be tested with netdevsim
// function will create netdevsim/netdevsim<random_id> virtual device that can be used for testing
// netdevsim module should be loaded to run devlink param tests
func setupDevlinkDeviceParamTest(t *testing.T) (string, string, func()) {
t.Helper()
skipUnlessRoot(t)
skipUnlessKModuleLoaded(t, "netdevsim")
testDevID := strconv.Itoa(1000 + rand.Intn(1000))
err := os.WriteFile("/sys/bus/netdevsim/new_device", []byte(testDevID), 0755)
if err != nil {
t.Fatalf("can't create netdevsim test device %s: %v", testDevID, err)
}

return "netdevsim", "netdevsim" + testDevID, func() {
_ = os.WriteFile("/sys/bus/netdevsim/del_device", []byte(testDevID), 0755)
}
}

func TestDevlinkGetDeviceParams(t *testing.T) {
busName, deviceName, cleanupFunc := setupDevlinkDeviceParamTest(t)
defer cleanupFunc()
params, err := DevlinkGetDeviceParams(busName, deviceName)
if err != nil {
t.Fatalf("failed to get device(%s/%s) parameters. %s", busName, deviceName, err)
}
if len(params) == 0 {
t.Fatal("parameters list is empty")
}
for _, p := range params {
validateDeviceParams(t, p)
}
}

func TestDevlinkGetDeviceParamByName(t *testing.T) {
busName, deviceName, cleanupFunc := setupDevlinkDeviceParamTest(t)
defer cleanupFunc()
param, err := DevlinkGetDeviceParamByName(busName, deviceName, "max_macs")
if err != nil {
t.Fatalf("failed to get device(%s/%s) parameter max_macs. %s", busName, deviceName, err)
}
validateDeviceParams(t, param)
}

func TestDevlinkSetDeviceParam(t *testing.T) {
busName, deviceName, cleanupFunc := setupDevlinkDeviceParamTest(t)
defer cleanupFunc()
err := DevlinkSetDeviceParam(busName, deviceName, "max_macs", nl.DEVLINK_PARAM_CMODE_DRIVERINIT, uint32(8))
if err != nil {
t.Fatalf("failed to set max_macs for device(%s/%s): %s", busName, deviceName, err)
}
param, err := DevlinkGetDeviceParamByName(busName, deviceName, "max_macs")
if err != nil {
t.Fatalf("failed to get device(%s/%s) parameter max_macs. %s", busName, deviceName, err)
}
validateDeviceParams(t, param)
v, ok := param.Values[0].Data.(uint32)
if !ok {
t.Fatalf("unexpected value")
}
if v != uint32(8) {
t.Fatalf("value not set")
}
}

func validateDeviceParams(t *testing.T, p *DevlinkParam) {
if p.Name == "" {
t.Fatal("Name field not set")
}
if p.Name == "max_macs" && !p.IsGeneric {
t.Fatal("IsGeneric should be true for generic parameter")
}
// test1 is a driver-specific parameter in netdevsim device, check should
// also path on HW devices
if p.Name == "test1" && p.IsGeneric {
t.Fatal("IsGeneric should be false for driver-specific parameter")
}
switch p.Type {
case nl.DEVLINK_PARAM_TYPE_U8,
nl.DEVLINK_PARAM_TYPE_U16,
nl.DEVLINK_PARAM_TYPE_U32,
nl.DEVLINK_PARAM_TYPE_STRING,
nl.DEVLINK_PARAM_TYPE_BOOL:
default:
t.Fatal("Type has unexpected value")
}
if len(p.Values) == 0 {
t.Fatal("Values are not set")
}
for _, v := range p.Values {
switch v.CMODE {
case nl.DEVLINK_PARAM_CMODE_RUNTIME,
nl.DEVLINK_PARAM_CMODE_DRIVERINIT,
nl.DEVLINK_PARAM_CMODE_PERMANENT:
default:
t.Fatal("CMODE has unexpected value")
}
if p.Name == "max_macs" {
_, ok := v.Data.(uint32)
if !ok {
t.Fatalf("value max_macs has wrong type: %T, expected: uint32", v.Data)
}
}
if p.Name == "test1" {
_, ok := v.Data.(bool)
if !ok {
t.Fatalf("value test1 has wrong type: %T, expected: bool", v.Data)
}
}
}
}
Loading

0 comments on commit 5daafaf

Please sign in to comment.