1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3
4from lib.py import ksft_disruptive, ksft_exit, ksft_run
5from lib.py import ksft_eq, ksft_raises, KsftSkipEx, KsftFailEx
6from lib.py import EthtoolFamily, NetdevFamily, NlError
7from lib.py import NetDrvEnv
8from lib.py import cmd, defer, ip
9import errno
10import glob
11import os
12import socket
13import struct
14import subprocess
15
16def sys_get_queues(ifname, qtype='rx') -> int:
17    folders = glob.glob(f'/sys/class/net/{ifname}/queues/{qtype}-*')
18    return len(folders)
19
20
21def nl_get_queues(cfg, nl, qtype='rx'):
22    queues = nl.queue_get({'ifindex': cfg.ifindex}, dump=True)
23    if queues:
24        return len([q for q in queues if q['type'] == qtype])
25    return None
26
27def check_xdp(cfg, nl, xdp_queue_id=0) -> None:
28    test_dir = os.path.dirname(os.path.realpath(__file__))
29    xdp = subprocess.Popen([f"{test_dir}/xdp_helper", f"{cfg.ifindex}", f"{xdp_queue_id}"],
30                           stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=1,
31                           text=True)
32    defer(xdp.kill)
33
34    stdout, stderr = xdp.communicate(timeout=10)
35    rx = tx = False
36
37    if xdp.returncode == 255:
38        raise KsftSkipEx('AF_XDP unsupported')
39    elif xdp.returncode > 0:
40        raise KsftFailEx('unable to create AF_XDP socket')
41
42    queues = nl.queue_get({'ifindex': cfg.ifindex}, dump=True)
43    if not queues:
44        raise KsftSkipEx("Netlink reports no queues")
45
46    for q in queues:
47        if q['id'] == 0:
48            if q['type'] == 'rx':
49                rx = True
50            if q['type'] == 'tx':
51                tx = True
52
53            ksft_eq(q['xsk'], {})
54        else:
55            if 'xsk' in q:
56                _fail("Check failed: xsk attribute set.")
57
58    ksft_eq(rx, True)
59    ksft_eq(tx, True)
60
61def get_queues(cfg, nl) -> None:
62    snl = NetdevFamily(recv_size=4096)
63
64    for qtype in ['rx', 'tx']:
65        queues = nl_get_queues(cfg, snl, qtype)
66        if not queues:
67            raise KsftSkipEx('queue-get not supported by device')
68
69        expected = sys_get_queues(cfg.dev['ifname'], qtype)
70        ksft_eq(queues, expected)
71
72
73def addremove_queues(cfg, nl) -> None:
74    queues = nl_get_queues(cfg, nl)
75    if not queues:
76        raise KsftSkipEx('queue-get not supported by device')
77
78    curr_queues = sys_get_queues(cfg.dev['ifname'])
79    if curr_queues == 1:
80        raise KsftSkipEx('cannot decrement queue: already at 1')
81
82    netnl = EthtoolFamily()
83    channels = netnl.channels_get({'header': {'dev-index': cfg.ifindex}})
84    if channels['combined-count'] == 0:
85        rx_type = 'rx'
86    else:
87        rx_type = 'combined'
88
89    expected = curr_queues - 1
90    cmd(f"ethtool -L {cfg.dev['ifname']} {rx_type} {expected}", timeout=10)
91    queues = nl_get_queues(cfg, nl)
92    ksft_eq(queues, expected)
93
94    expected = curr_queues
95    cmd(f"ethtool -L {cfg.dev['ifname']} {rx_type} {expected}", timeout=10)
96    queues = nl_get_queues(cfg, nl)
97    ksft_eq(queues, expected)
98
99
100@ksft_disruptive
101def check_down(cfg, nl) -> None:
102    # Check the NAPI IDs before interface goes down and hides them
103    napis = nl.napi_get({'ifindex': cfg.ifindex}, dump=True)
104
105    ip(f"link set dev {cfg.dev['ifname']} down")
106    defer(ip, f"link set dev {cfg.dev['ifname']} up")
107
108    with ksft_raises(NlError) as cm:
109        nl.queue_get({'ifindex': cfg.ifindex, 'id': 0, 'type': 'rx'})
110    ksft_eq(cm.exception.nl_msg.error, -errno.ENOENT)
111
112    if napis:
113        with ksft_raises(NlError) as cm:
114            nl.napi_get({'id': napis[0]['id']})
115        ksft_eq(cm.exception.nl_msg.error, -errno.ENOENT)
116
117
118def main() -> None:
119    with NetDrvEnv(__file__, queue_count=100) as cfg:
120        ksft_run([get_queues, addremove_queues, check_down, check_xdp], args=(cfg, NetdevFamily()))
121    ksft_exit()
122
123
124if __name__ == "__main__":
125    main()
126