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
2125173dd4SPo-Hsu Lin# Kselftest framework requirement - SKIP code is 4
2225173dd4SPo-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
62*24994513SPo-Hsu Lin        validate_devlink_output(ports, 'flavour')
63*24994513SPo-Hsu Lin
64f3348a82SDanielle Ratson        for port in ports:
65f3348a82SDanielle Ratson            if dev in port:
66f3348a82SDanielle Ratson                if ports[port]['flavour'] == 'physical':
67f3348a82SDanielle Ratson                    arr.append(Port(bus_info=port, name=ports[port]['netdev']))
68f3348a82SDanielle Ratson
69f3348a82SDanielle Ratson        return arr
70f3348a82SDanielle Ratson
71f3348a82SDanielle Ratson    def __init__(self, dev):
72f3348a82SDanielle Ratson        self.if_names = devlink_ports.get_if_names(dev)
73f3348a82SDanielle Ratson
74f3348a82SDanielle Ratson
75f3348a82SDanielle Ratsondef get_max_lanes(port):
76f3348a82SDanielle Ratson    """
77f3348a82SDanielle Ratson    Get the $port's maximum number of lanes.
78f3348a82SDanielle Ratson    Return: number of lanes, e.g. 1, 2, 4 and 8.
79f3348a82SDanielle Ratson    """
80f3348a82SDanielle Ratson
81f3348a82SDanielle Ratson    cmd = "devlink -j port show %s" % port
82f3348a82SDanielle Ratson    stdout, stderr = run_command(cmd)
83f3348a82SDanielle Ratson    assert stderr == ""
84f3348a82SDanielle Ratson    values = list(json.loads(stdout)['port'].values())[0]
85f3348a82SDanielle Ratson
86f3348a82SDanielle Ratson    if 'lanes' in values:
87f3348a82SDanielle Ratson        lanes = values['lanes']
88f3348a82SDanielle Ratson    else:
89f3348a82SDanielle Ratson        lanes = 0
90f3348a82SDanielle Ratson    return lanes
91f3348a82SDanielle Ratson
92f3348a82SDanielle Ratson
93f3348a82SDanielle Ratsondef get_split_ability(port):
94f3348a82SDanielle Ratson    """
95f3348a82SDanielle Ratson    Get the $port split ability.
96f3348a82SDanielle Ratson    Return: split ability, true or false.
97f3348a82SDanielle Ratson    """
98f3348a82SDanielle Ratson
99f3348a82SDanielle Ratson    cmd = "devlink -j port show %s" % port.name
100f3348a82SDanielle Ratson    stdout, stderr = run_command(cmd)
101f3348a82SDanielle Ratson    assert stderr == ""
102f3348a82SDanielle Ratson    values = list(json.loads(stdout)['port'].values())[0]
103f3348a82SDanielle Ratson
104f3348a82SDanielle Ratson    return values['splittable']
105f3348a82SDanielle Ratson
106f3348a82SDanielle Ratson
107f3348a82SDanielle Ratsondef split(k, port, should_fail=False):
108f3348a82SDanielle Ratson    """
109f3348a82SDanielle Ratson    Split $port into $k ports.
110f3348a82SDanielle Ratson    If should_fail == True, the split should fail. Otherwise, should pass.
111f3348a82SDanielle Ratson    Return: Array of sub ports after splitting.
112f3348a82SDanielle Ratson            If the $port wasn't split, the array will be empty.
113f3348a82SDanielle Ratson    """
114f3348a82SDanielle Ratson
115f3348a82SDanielle Ratson    cmd = "devlink port split %s count %s" % (port.bus_info, k)
116f3348a82SDanielle Ratson    stdout, stderr = run_command(cmd, should_fail=should_fail)
117f3348a82SDanielle Ratson
118f3348a82SDanielle Ratson    if should_fail:
119f3348a82SDanielle Ratson        if not test(stderr != "", "%s is unsplittable" % port.name):
120f3348a82SDanielle Ratson            print("split an unsplittable port %s" % port.name)
121f3348a82SDanielle Ratson            return create_split_group(port, k)
122f3348a82SDanielle Ratson    else:
123f3348a82SDanielle Ratson        if stderr == "":
124f3348a82SDanielle Ratson            return create_split_group(port, k)
125f3348a82SDanielle Ratson        print("didn't split a splittable port %s" % port.name)
126f3348a82SDanielle Ratson
127f3348a82SDanielle Ratson    return []
128f3348a82SDanielle Ratson
129f3348a82SDanielle Ratson
130f3348a82SDanielle Ratsondef unsplit(port):
131f3348a82SDanielle Ratson    """
132f3348a82SDanielle Ratson    Unsplit $port.
133f3348a82SDanielle Ratson    """
134f3348a82SDanielle Ratson
135f3348a82SDanielle Ratson    cmd = "devlink port unsplit %s" % port
136f3348a82SDanielle Ratson    stdout, stderr = run_command(cmd)
137f3348a82SDanielle Ratson    test(stderr == "", "Unsplit port %s" % port)
138f3348a82SDanielle Ratson
139f3348a82SDanielle Ratson
140f3348a82SDanielle Ratsondef exists(port, dev):
141f3348a82SDanielle Ratson    """
142f3348a82SDanielle Ratson    Check if $port exists in the devlink ports.
143f3348a82SDanielle Ratson    Return: True is so, False otherwise.
144f3348a82SDanielle Ratson    """
145f3348a82SDanielle Ratson
146f3348a82SDanielle Ratson    return any(dev_port.name == port
147f3348a82SDanielle Ratson               for dev_port in devlink_ports.get_if_names(dev))
148f3348a82SDanielle Ratson
149f3348a82SDanielle Ratson
150f3348a82SDanielle Ratsondef exists_and_lanes(ports, lanes, dev):
151f3348a82SDanielle Ratson    """
152f3348a82SDanielle Ratson    Check if every port in the list $ports exists in the devlink ports and has
153f3348a82SDanielle Ratson    $lanes number of lanes after splitting.
154f3348a82SDanielle Ratson    Return: True if both are True, False otherwise.
155f3348a82SDanielle Ratson    """
156f3348a82SDanielle Ratson
157f3348a82SDanielle Ratson    for port in ports:
158f3348a82SDanielle Ratson        max_lanes = get_max_lanes(port)
159f3348a82SDanielle Ratson        if not exists(port, dev):
160f3348a82SDanielle Ratson            print("port %s doesn't exist in devlink ports" % port)
161f3348a82SDanielle Ratson            return False
162f3348a82SDanielle Ratson        if max_lanes != lanes:
163f3348a82SDanielle Ratson            print("port %s has %d lanes, but %s were expected"
164f3348a82SDanielle Ratson                  % (port, lanes, max_lanes))
165f3348a82SDanielle Ratson            return False
166f3348a82SDanielle Ratson    return True
167f3348a82SDanielle Ratson
168f3348a82SDanielle Ratson
169f3348a82SDanielle Ratsondef test(cond, msg):
170f3348a82SDanielle Ratson    """
171f3348a82SDanielle Ratson    Check $cond and print a message accordingly.
172f3348a82SDanielle Ratson    Return: True is pass, False otherwise.
173f3348a82SDanielle Ratson    """
174f3348a82SDanielle Ratson
175f3348a82SDanielle Ratson    if cond:
176f3348a82SDanielle Ratson        print("TEST: %-60s [ OK ]" % msg)
177f3348a82SDanielle Ratson    else:
178f3348a82SDanielle Ratson        print("TEST: %-60s [FAIL]" % msg)
179f3348a82SDanielle Ratson
180f3348a82SDanielle Ratson    return cond
181f3348a82SDanielle Ratson
182f3348a82SDanielle Ratson
183f3348a82SDanielle Ratsondef create_split_group(port, k):
184f3348a82SDanielle Ratson    """
185f3348a82SDanielle Ratson    Create the split group for $port.
186f3348a82SDanielle Ratson    Return: Array with $k elements, which are the split port group.
187f3348a82SDanielle Ratson    """
188f3348a82SDanielle Ratson
189f3348a82SDanielle Ratson    return list(port.name + "s" + str(i) for i in range(k))
190f3348a82SDanielle Ratson
191f3348a82SDanielle Ratson
192f3348a82SDanielle Ratsondef split_unsplittable_port(port, k):
193f3348a82SDanielle Ratson    """
194f3348a82SDanielle Ratson    Test that splitting of unsplittable port fails.
195f3348a82SDanielle Ratson    """
196f3348a82SDanielle Ratson
197f3348a82SDanielle Ratson    # split to max
198f3348a82SDanielle Ratson    new_split_group = split(k, port, should_fail=True)
199f3348a82SDanielle Ratson
200f3348a82SDanielle Ratson    if new_split_group != []:
201f3348a82SDanielle Ratson        unsplit(port.bus_info)
202f3348a82SDanielle Ratson
203f3348a82SDanielle Ratson
204f3348a82SDanielle Ratsondef split_splittable_port(port, k, lanes, dev):
205f3348a82SDanielle Ratson    """
206f3348a82SDanielle Ratson    Test that splitting of splittable port passes correctly.
207f3348a82SDanielle Ratson    """
208f3348a82SDanielle Ratson
209f3348a82SDanielle Ratson    new_split_group = split(k, port)
210f3348a82SDanielle Ratson
211f3348a82SDanielle Ratson    # Once the split command ends, it takes some time to the sub ifaces'
212f3348a82SDanielle Ratson    # to get their names. Use udevadm to continue only when all current udev
213f3348a82SDanielle Ratson    # events are handled.
214f3348a82SDanielle Ratson    cmd = "udevadm settle"
215f3348a82SDanielle Ratson    stdout, stderr = run_command(cmd)
216f3348a82SDanielle Ratson    assert stderr == ""
217f3348a82SDanielle Ratson
218f3348a82SDanielle Ratson    if new_split_group != []:
219f3348a82SDanielle Ratson        test(exists_and_lanes(new_split_group, lanes/k, dev),
220f3348a82SDanielle Ratson             "split port %s into %s" % (port.name, k))
221f3348a82SDanielle Ratson
222f3348a82SDanielle Ratson    unsplit(port.bus_info)
223f3348a82SDanielle Ratson
224f3348a82SDanielle Ratson
225*24994513SPo-Hsu Lindef validate_devlink_output(devlink_data, target_property=None):
226*24994513SPo-Hsu Lin    """
227*24994513SPo-Hsu Lin    Determine if test should be skipped by checking:
228*24994513SPo-Hsu Lin      1. devlink_data contains values
229*24994513SPo-Hsu Lin      2. The target_property exist in devlink_data
230*24994513SPo-Hsu Lin    """
231*24994513SPo-Hsu Lin    skip_reason = None
232*24994513SPo-Hsu Lin    if any(devlink_data.values()):
233*24994513SPo-Hsu Lin        if target_property:
234*24994513SPo-Hsu Lin            skip_reason = "{} not found in devlink output, test skipped".format(target_property)
235*24994513SPo-Hsu Lin            for key in devlink_data:
236*24994513SPo-Hsu Lin                if target_property in devlink_data[key]:
237*24994513SPo-Hsu Lin                    skip_reason = None
238*24994513SPo-Hsu Lin    else:
239*24994513SPo-Hsu Lin        skip_reason = 'devlink output is empty, test skipped'
240*24994513SPo-Hsu Lin
241*24994513SPo-Hsu Lin    if skip_reason:
242*24994513SPo-Hsu Lin        print(skip_reason)
243*24994513SPo-Hsu Lin        sys.exit(KSFT_SKIP)
244*24994513SPo-Hsu Lin
245*24994513SPo-Hsu Lin
246f3348a82SDanielle Ratsondef make_parser():
247f3348a82SDanielle Ratson    parser = argparse.ArgumentParser(description='A test for port splitting.')
248f3348a82SDanielle Ratson    parser.add_argument('--dev',
249f3348a82SDanielle Ratson                        help='The devlink handle of the device under test. ' +
250f3348a82SDanielle Ratson                             'The default is the first registered devlink ' +
251f3348a82SDanielle Ratson                             'handle.')
252f3348a82SDanielle Ratson
253f3348a82SDanielle Ratson    return parser
254f3348a82SDanielle Ratson
255f3348a82SDanielle Ratson
256f3348a82SDanielle Ratsondef main(cmdline=None):
257f3348a82SDanielle Ratson    parser = make_parser()
258f3348a82SDanielle Ratson    args = parser.parse_args(cmdline)
259f3348a82SDanielle Ratson
260f3348a82SDanielle Ratson    dev = args.dev
261f3348a82SDanielle Ratson    if not dev:
262f3348a82SDanielle Ratson        cmd = "devlink -j dev show"
263f3348a82SDanielle Ratson        stdout, stderr = run_command(cmd)
264f3348a82SDanielle Ratson        assert stderr == ""
265f3348a82SDanielle Ratson
266*24994513SPo-Hsu Lin        validate_devlink_output(json.loads(stdout))
267f3348a82SDanielle Ratson        devs = json.loads(stdout)['dev']
268f3348a82SDanielle Ratson        dev = list(devs.keys())[0]
269f3348a82SDanielle Ratson
270f3348a82SDanielle Ratson    cmd = "devlink dev show %s" % dev
271f3348a82SDanielle Ratson    stdout, stderr = run_command(cmd)
272f3348a82SDanielle Ratson    if stderr != "":
273f3348a82SDanielle Ratson        print("devlink device %s can not be found" % dev)
274f3348a82SDanielle Ratson        sys.exit(1)
275f3348a82SDanielle Ratson
276f3348a82SDanielle Ratson    ports = devlink_ports(dev)
277f3348a82SDanielle Ratson
278*24994513SPo-Hsu Lin    found_max_lanes = False
279f3348a82SDanielle Ratson    for port in ports.if_names:
280f3348a82SDanielle Ratson        max_lanes = get_max_lanes(port.name)
281f3348a82SDanielle Ratson
282f3348a82SDanielle Ratson        # If max lanes is 0, do not test port splitting at all
283f3348a82SDanielle Ratson        if max_lanes == 0:
284f3348a82SDanielle Ratson            continue
285f3348a82SDanielle Ratson
286f3348a82SDanielle Ratson        # If 1 lane, shouldn't be able to split
287f3348a82SDanielle Ratson        elif max_lanes == 1:
288f3348a82SDanielle Ratson            test(not get_split_ability(port),
289f3348a82SDanielle Ratson                 "%s should not be able to split" % port.name)
290f3348a82SDanielle Ratson            split_unsplittable_port(port, max_lanes)
291f3348a82SDanielle Ratson
292f3348a82SDanielle Ratson        # Else, splitting should pass and all the split ports should exist.
293f3348a82SDanielle Ratson        else:
294f3348a82SDanielle Ratson            lane = max_lanes
295f3348a82SDanielle Ratson            test(get_split_ability(port),
296f3348a82SDanielle Ratson                 "%s should be able to split" % port.name)
297f3348a82SDanielle Ratson            while lane > 1:
298f3348a82SDanielle Ratson                split_splittable_port(port, lane, max_lanes, dev)
299f3348a82SDanielle Ratson
300f3348a82SDanielle Ratson                lane //= 2
301*24994513SPo-Hsu Lin        found_max_lanes = True
302*24994513SPo-Hsu Lin
303*24994513SPo-Hsu Lin    if not found_max_lanes:
304*24994513SPo-Hsu Lin        print(f"Test not started, no port of device {dev} reports max_lanes")
305*24994513SPo-Hsu Lin        sys.exit(KSFT_SKIP)
306f3348a82SDanielle Ratson
307f3348a82SDanielle Ratson
308f3348a82SDanielle Ratsonif __name__ == "__main__":
309f3348a82SDanielle Ratson    main()
310