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_not_in, ksft_raises, KsftSkipEx, KsftFailEx
6from lib.py import EthtoolFamily, NetdevFamily, NlError
7from lib.py import NetDrvEnv
8from lib.py import bkg, cmd, defer, ip
9import errno
10import glob
11import os
12import socket
13import struct
14
15def sys_get_queues(ifname, qtype='rx') -> int:
16    folders = glob.glob(f'/sys/class/net/{ifname}/queues/{qtype}-*')
17    return len(folders)
18
19
20def nl_get_queues(cfg, nl, qtype='rx'):
21    queues = nl.queue_get({'ifindex': cfg.ifindex}, dump=True)
22    if queues:
23        return len([q for q in queues if q['type'] == qtype])
24    return None
25
26
27def check_xsk(cfg, nl, xdp_queue_id=0) -> None:
28    # Probe for support
29    xdp = cmd(cfg.rpath("xdp_helper") + ' - -', fail=False)
30    if xdp.ret == 255:
31        raise KsftSkipEx('AF_XDP unsupported')
32    elif xdp.ret > 0:
33        raise KsftFailEx('unable to create AF_XDP socket')
34
35    with bkg(f'{cfg.rpath("xdp_helper")} {cfg.ifindex} {xdp_queue_id}',
36             ksft_wait=3):
37
38        rx = tx = False
39
40        queues = nl.queue_get({'ifindex': cfg.ifindex}, dump=True)
41        if not queues:
42            raise KsftSkipEx("Netlink reports no queues")
43
44        for q in queues:
45            if q['id'] == 0:
46                if q['type'] == 'rx':
47                    rx = True
48                if q['type'] == 'tx':
49                    tx = True
50
51                ksft_eq(q.get('xsk', None), {},
52                        comment="xsk attr on queue we configured")
53            else:
54                ksft_not_in('xsk', q,
55                            comment="xsk attr on queue we didn't configure")
56
57        ksft_eq(rx, True)
58        ksft_eq(tx, True)
59
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_xsk],
121                 args=(cfg, NetdevFamily()))
122    ksft_exit()
123
124
125if __name__ == "__main__":
126    main()
127