1c25ce589SFinn Behrens#!/usr/bin/env python3
2f3348a82SDanielle Ratson# SPDX-License-Identifier: GPL-2.0
3f3348a82SDanielle Ratson
4f3348a82SDanielle Ratsonfrom subprocess import PIPE, Popen
5f3348a82SDanielle Ratsonimport json
6f3348a82SDanielle Ratsonimport time
7f3348a82SDanielle Ratsonimport argparse
8f3348a82SDanielle Ratsonimport collections
9f3348a82SDanielle Ratsonimport sys
10f3348a82SDanielle Ratson
11f3348a82SDanielle Ratson#
12f3348a82SDanielle Ratson# Test port split configuration using devlink-port lanes attribute.
13f3348a82SDanielle Ratson# The test is skipped in case the attribute is not available.
14f3348a82SDanielle Ratson#
15f3348a82SDanielle Ratson# First, check that all the ports with 1 lane fail to split.
16f3348a82SDanielle Ratson# Second, check that all the ports with more than 1 lane can be split
17f3348a82SDanielle Ratson# to all valid configurations (e.g., split to 2, split to 4 etc.)
18f3348a82SDanielle Ratson#
19f3348a82SDanielle Ratson
20f3348a82SDanielle Ratson
21*25173dd4SPo-Hsu Lin# Kselftest framework requirement - SKIP code is 4
22*25173dd4SPo-Hsu LinKSFT_SKIP=4
23f3348a82SDanielle RatsonPort = collections.namedtuple('Port', 'bus_info name')
24f3348a82SDanielle Ratson
25f3348a82SDanielle Ratson
26f3348a82SDanielle Ratsondef run_command(cmd, should_fail=False):
27f3348a82SDanielle Ratson    """
28f3348a82SDanielle Ratson    Run a command in subprocess.
29f3348a82SDanielle Ratson    Return: Tuple of (stdout, stderr).
30f3348a82SDanielle Ratson    """
31f3348a82SDanielle Ratson
32f3348a82SDanielle Ratson    p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True)
33f3348a82SDanielle Ratson    stdout, stderr = p.communicate()
34f3348a82SDanielle Ratson    stdout, stderr = stdout.decode(), stderr.decode()
35f3348a82SDanielle Ratson
36f3348a82SDanielle Ratson    if stderr != "" and not should_fail:
37f3348a82SDanielle Ratson        print("Error sending command: %s" % cmd)
38f3348a82SDanielle Ratson        print(stdout)
39f3348a82SDanielle Ratson        print(stderr)
40f3348a82SDanielle Ratson    return stdout, stderr
41f3348a82SDanielle Ratson
42f3348a82SDanielle Ratson
43f3348a82SDanielle Ratsonclass devlink_ports(object):
44f3348a82SDanielle Ratson    """
45f3348a82SDanielle Ratson    Class that holds information on the devlink ports, required to the tests;
46f3348a82SDanielle Ratson    if_names: A list of interfaces in the devlink ports.
47f3348a82SDanielle Ratson    """
48f3348a82SDanielle Ratson
49f3348a82SDanielle Ratson    def get_if_names(dev):
50f3348a82SDanielle Ratson        """
51f3348a82SDanielle Ratson        Get a list of physical devlink ports.
52f3348a82SDanielle Ratson        Return: Array of tuples (bus_info/port, if_name).
53f3348a82SDanielle Ratson        """
54f3348a82SDanielle Ratson
55f3348a82SDanielle Ratson        arr = []
56f3348a82SDanielle Ratson
57f3348a82SDanielle Ratson        cmd = "devlink -j port show"
58f3348a82SDanielle Ratson        stdout, stderr = run_command(cmd)
59f3348a82SDanielle Ratson        assert stderr == ""
60f3348a82SDanielle Ratson        ports = json.loads(stdout)['port']
61f3348a82SDanielle Ratson
62f3348a82SDanielle Ratson        for port in ports:
63f3348a82SDanielle Ratson            if dev in port:
64f3348a82SDanielle Ratson                if ports[port]['flavour'] == 'physical':
65f3348a82SDanielle Ratson                    arr.append(Port(bus_info=port, name=ports[port]['netdev']))
66f3348a82SDanielle Ratson
67f3348a82SDanielle Ratson        return arr
68f3348a82SDanielle Ratson
69f3348a82SDanielle Ratson    def __init__(self, dev):
70f3348a82SDanielle Ratson        self.if_names = devlink_ports.get_if_names(dev)
71f3348a82SDanielle Ratson
72f3348a82SDanielle Ratson
73f3348a82SDanielle Ratsondef get_max_lanes(port):
74f3348a82SDanielle Ratson    """
75f3348a82SDanielle Ratson    Get the $port's maximum number of lanes.
76f3348a82SDanielle Ratson    Return: number of lanes, e.g. 1, 2, 4 and 8.
77f3348a82SDanielle Ratson    """
78f3348a82SDanielle Ratson
79f3348a82SDanielle Ratson    cmd = "devlink -j port show %s" % port
80f3348a82SDanielle Ratson    stdout, stderr = run_command(cmd)
81f3348a82SDanielle Ratson    assert stderr == ""
82f3348a82SDanielle Ratson    values = list(json.loads(stdout)['port'].values())[0]
83f3348a82SDanielle Ratson
84f3348a82SDanielle Ratson    if 'lanes' in values:
85f3348a82SDanielle Ratson        lanes = values['lanes']
86f3348a82SDanielle Ratson    else:
87f3348a82SDanielle Ratson        lanes = 0
88f3348a82SDanielle Ratson    return lanes
89f3348a82SDanielle Ratson
90f3348a82SDanielle Ratson
91f3348a82SDanielle Ratsondef get_split_ability(port):
92f3348a82SDanielle Ratson    """
93f3348a82SDanielle Ratson    Get the $port split ability.
94f3348a82SDanielle Ratson    Return: split ability, true or false.
95f3348a82SDanielle Ratson    """
96f3348a82SDanielle Ratson
97f3348a82SDanielle Ratson    cmd = "devlink -j port show %s" % port.name
98f3348a82SDanielle Ratson    stdout, stderr = run_command(cmd)
99f3348a82SDanielle Ratson    assert stderr == ""
100f3348a82SDanielle Ratson    values = list(json.loads(stdout)['port'].values())[0]
101f3348a82SDanielle Ratson
102f3348a82SDanielle Ratson    return values['splittable']
103f3348a82SDanielle Ratson
104f3348a82SDanielle Ratson
105f3348a82SDanielle Ratsondef split(k, port, should_fail=False):
106f3348a82SDanielle Ratson    """
107f3348a82SDanielle Ratson    Split $port into $k ports.
108f3348a82SDanielle Ratson    If should_fail == True, the split should fail. Otherwise, should pass.
109f3348a82SDanielle Ratson    Return: Array of sub ports after splitting.
110f3348a82SDanielle Ratson            If the $port wasn't split, the array will be empty.
111f3348a82SDanielle Ratson    """
112f3348a82SDanielle Ratson
113f3348a82SDanielle Ratson    cmd = "devlink port split %s count %s" % (port.bus_info, k)
114f3348a82SDanielle Ratson    stdout, stderr = run_command(cmd, should_fail=should_fail)
115f3348a82SDanielle Ratson
116f3348a82SDanielle Ratson    if should_fail:
117f3348a82SDanielle Ratson        if not test(stderr != "", "%s is unsplittable" % port.name):
118f3348a82SDanielle Ratson            print("split an unsplittable port %s" % port.name)
119f3348a82SDanielle Ratson            return create_split_group(port, k)
120f3348a82SDanielle Ratson    else:
121f3348a82SDanielle Ratson        if stderr == "":
122f3348a82SDanielle Ratson            return create_split_group(port, k)
123f3348a82SDanielle Ratson        print("didn't split a splittable port %s" % port.name)
124f3348a82SDanielle Ratson
125f3348a82SDanielle Ratson    return []
126f3348a82SDanielle Ratson
127f3348a82SDanielle Ratson
128f3348a82SDanielle Ratsondef unsplit(port):
129f3348a82SDanielle Ratson    """
130f3348a82SDanielle Ratson    Unsplit $port.
131f3348a82SDanielle Ratson    """
132f3348a82SDanielle Ratson
133f3348a82SDanielle Ratson    cmd = "devlink port unsplit %s" % port
134f3348a82SDanielle Ratson    stdout, stderr = run_command(cmd)
135f3348a82SDanielle Ratson    test(stderr == "", "Unsplit port %s" % port)
136f3348a82SDanielle Ratson
137f3348a82SDanielle Ratson
138f3348a82SDanielle Ratsondef exists(port, dev):
139f3348a82SDanielle Ratson    """
140f3348a82SDanielle Ratson    Check if $port exists in the devlink ports.
141f3348a82SDanielle Ratson    Return: True is so, False otherwise.
142f3348a82SDanielle Ratson    """
143f3348a82SDanielle Ratson
144f3348a82SDanielle Ratson    return any(dev_port.name == port
145f3348a82SDanielle Ratson               for dev_port in devlink_ports.get_if_names(dev))
146f3348a82SDanielle Ratson
147f3348a82SDanielle Ratson
148f3348a82SDanielle Ratsondef exists_and_lanes(ports, lanes, dev):
149f3348a82SDanielle Ratson    """
150f3348a82SDanielle Ratson    Check if every port in the list $ports exists in the devlink ports and has
151f3348a82SDanielle Ratson    $lanes number of lanes after splitting.
152f3348a82SDanielle Ratson    Return: True if both are True, False otherwise.
153f3348a82SDanielle Ratson    """
154f3348a82SDanielle Ratson
155f3348a82SDanielle Ratson    for port in ports:
156f3348a82SDanielle Ratson        max_lanes = get_max_lanes(port)
157f3348a82SDanielle Ratson        if not exists(port, dev):
158f3348a82SDanielle Ratson            print("port %s doesn't exist in devlink ports" % port)
159f3348a82SDanielle Ratson            return False
160f3348a82SDanielle Ratson        if max_lanes != lanes:
161f3348a82SDanielle Ratson            print("port %s has %d lanes, but %s were expected"
162f3348a82SDanielle Ratson                  % (port, lanes, max_lanes))
163f3348a82SDanielle Ratson            return False
164f3348a82SDanielle Ratson    return True
165f3348a82SDanielle Ratson
166f3348a82SDanielle Ratson
167f3348a82SDanielle Ratsondef test(cond, msg):
168f3348a82SDanielle Ratson    """
169f3348a82SDanielle Ratson    Check $cond and print a message accordingly.
170f3348a82SDanielle Ratson    Return: True is pass, False otherwise.
171f3348a82SDanielle Ratson    """
172f3348a82SDanielle Ratson
173f3348a82SDanielle Ratson    if cond:
174f3348a82SDanielle Ratson        print("TEST: %-60s [ OK ]" % msg)
175f3348a82SDanielle Ratson    else:
176f3348a82SDanielle Ratson        print("TEST: %-60s [FAIL]" % msg)
177f3348a82SDanielle Ratson
178f3348a82SDanielle Ratson    return cond
179f3348a82SDanielle Ratson
180f3348a82SDanielle Ratson
181f3348a82SDanielle Ratsondef create_split_group(port, k):
182f3348a82SDanielle Ratson    """
183f3348a82SDanielle Ratson    Create the split group for $port.
184f3348a82SDanielle Ratson    Return: Array with $k elements, which are the split port group.
185f3348a82SDanielle Ratson    """
186f3348a82SDanielle Ratson
187f3348a82SDanielle Ratson    return list(port.name + "s" + str(i) for i in range(k))
188f3348a82SDanielle Ratson
189f3348a82SDanielle Ratson
190f3348a82SDanielle Ratsondef split_unsplittable_port(port, k):
191f3348a82SDanielle Ratson    """
192f3348a82SDanielle Ratson    Test that splitting of unsplittable port fails.
193f3348a82SDanielle Ratson    """
194f3348a82SDanielle Ratson
195f3348a82SDanielle Ratson    # split to max
196f3348a82SDanielle Ratson    new_split_group = split(k, port, should_fail=True)
197f3348a82SDanielle Ratson
198f3348a82SDanielle Ratson    if new_split_group != []:
199f3348a82SDanielle Ratson        unsplit(port.bus_info)
200f3348a82SDanielle Ratson
201f3348a82SDanielle Ratson
202f3348a82SDanielle Ratsondef split_splittable_port(port, k, lanes, dev):
203f3348a82SDanielle Ratson    """
204f3348a82SDanielle Ratson    Test that splitting of splittable port passes correctly.
205f3348a82SDanielle Ratson    """
206f3348a82SDanielle Ratson
207f3348a82SDanielle Ratson    new_split_group = split(k, port)
208f3348a82SDanielle Ratson
209f3348a82SDanielle Ratson    # Once the split command ends, it takes some time to the sub ifaces'
210f3348a82SDanielle Ratson    # to get their names. Use udevadm to continue only when all current udev
211f3348a82SDanielle Ratson    # events are handled.
212f3348a82SDanielle Ratson    cmd = "udevadm settle"
213f3348a82SDanielle Ratson    stdout, stderr = run_command(cmd)
214f3348a82SDanielle Ratson    assert stderr == ""
215f3348a82SDanielle Ratson
216f3348a82SDanielle Ratson    if new_split_group != []:
217f3348a82SDanielle Ratson        test(exists_and_lanes(new_split_group, lanes/k, dev),
218f3348a82SDanielle Ratson             "split port %s into %s" % (port.name, k))
219f3348a82SDanielle Ratson
220f3348a82SDanielle Ratson    unsplit(port.bus_info)
221f3348a82SDanielle Ratson
222f3348a82SDanielle Ratson
223f3348a82SDanielle Ratsondef make_parser():
224f3348a82SDanielle Ratson    parser = argparse.ArgumentParser(description='A test for port splitting.')
225f3348a82SDanielle Ratson    parser.add_argument('--dev',
226f3348a82SDanielle Ratson                        help='The devlink handle of the device under test. ' +
227f3348a82SDanielle Ratson                             'The default is the first registered devlink ' +
228f3348a82SDanielle Ratson                             'handle.')
229f3348a82SDanielle Ratson
230f3348a82SDanielle Ratson    return parser
231f3348a82SDanielle Ratson
232f3348a82SDanielle Ratson
233f3348a82SDanielle Ratsondef main(cmdline=None):
234f3348a82SDanielle Ratson    parser = make_parser()
235f3348a82SDanielle Ratson    args = parser.parse_args(cmdline)
236f3348a82SDanielle Ratson
237f3348a82SDanielle Ratson    dev = args.dev
238f3348a82SDanielle Ratson    if not dev:
239f3348a82SDanielle Ratson        cmd = "devlink -j dev show"
240f3348a82SDanielle Ratson        stdout, stderr = run_command(cmd)
241f3348a82SDanielle Ratson        assert stderr == ""
242f3348a82SDanielle Ratson
243f3348a82SDanielle Ratson        devs = json.loads(stdout)['dev']
244*25173dd4SPo-Hsu Lin        if devs:
245f3348a82SDanielle Ratson            dev = list(devs.keys())[0]
246*25173dd4SPo-Hsu Lin        else:
247*25173dd4SPo-Hsu Lin            print("no devlink device was found, test skipped")
248*25173dd4SPo-Hsu Lin            sys.exit(KSFT_SKIP)
249f3348a82SDanielle Ratson
250f3348a82SDanielle Ratson    cmd = "devlink dev show %s" % dev
251f3348a82SDanielle Ratson    stdout, stderr = run_command(cmd)
252f3348a82SDanielle Ratson    if stderr != "":
253f3348a82SDanielle Ratson        print("devlink device %s can not be found" % dev)
254f3348a82SDanielle Ratson        sys.exit(1)
255f3348a82SDanielle Ratson
256f3348a82SDanielle Ratson    ports = devlink_ports(dev)
257f3348a82SDanielle Ratson
258f3348a82SDanielle Ratson    for port in ports.if_names:
259f3348a82SDanielle Ratson        max_lanes = get_max_lanes(port.name)
260f3348a82SDanielle Ratson
261f3348a82SDanielle Ratson        # If max lanes is 0, do not test port splitting at all
262f3348a82SDanielle Ratson        if max_lanes == 0:
263f3348a82SDanielle Ratson            continue
264f3348a82SDanielle Ratson
265f3348a82SDanielle Ratson        # If 1 lane, shouldn't be able to split
266f3348a82SDanielle Ratson        elif max_lanes == 1:
267f3348a82SDanielle Ratson            test(not get_split_ability(port),
268f3348a82SDanielle Ratson                 "%s should not be able to split" % port.name)
269f3348a82SDanielle Ratson            split_unsplittable_port(port, max_lanes)
270f3348a82SDanielle Ratson
271f3348a82SDanielle Ratson        # Else, splitting should pass and all the split ports should exist.
272f3348a82SDanielle Ratson        else:
273f3348a82SDanielle Ratson            lane = max_lanes
274f3348a82SDanielle Ratson            test(get_split_ability(port),
275f3348a82SDanielle Ratson                 "%s should be able to split" % port.name)
276f3348a82SDanielle Ratson            while lane > 1:
277f3348a82SDanielle Ratson                split_splittable_port(port, lane, max_lanes, dev)
278f3348a82SDanielle Ratson
279f3348a82SDanielle Ratson                lane //= 2
280f3348a82SDanielle Ratson
281f3348a82SDanielle Ratson
282f3348a82SDanielle Ratsonif __name__ == "__main__":
283f3348a82SDanielle Ratson    main()
284