1# SPDX-License-Identifier: GPL-2.0
2# (C) Copyright 2023, Advanced Micro Devices, Inc.
3
4"""
5Note: This test relies on boardenv_* containing configuration values to define
6which the network environment available for testing. Without this, this test
7will be automatically skipped.
8
9For example:
10
11.. code-block:: python
12
13   # Details regarding a boot image file that may be read from a TFTP server. This
14   # variable may be omitted or set to None if TFTP boot testing is not possible
15   # or desired.
16   env__net_tftp_bootable_file = {
17       'fn': 'image.ub',
18       'addr': 0x10000000,
19       'size': 5058624,
20       'crc32': 'c2244b26',
21       'pattern': 'Linux',
22       'config': 'config@2',
23       'timeout': 50000,
24       'check_type': 'boot_error',
25       'check_pattern': 'ERROR',
26   }
27
28   # False or omitted if a TFTP boot test should be tested.
29   # If TFTP boot testing is not possible or desired, set this variable to True.
30   # For example: If FIT image is not proper to boot
31   env__tftp_boot_test_skip = False
32
33
34Here is the example of FIT image configurations:
35
36.. code-block:: devicetree
37
38   configurations {
39       default = "config@1";
40       config@1 {
41           description = "Boot Linux kernel with config@1";
42           kernel = "kernel@0";
43           fdt = "fdt@0";
44           ramdisk = "ramdisk@0";
45           hash@1 {
46               algo = "sha1";
47           };
48       };
49       config@2 {
50           description = "Boot Linux kernel with config@2";
51           kernel = "kernel@1";
52           fdt = "fdt@1";
53           ramdisk = "ramdisk@1";
54           hash@1 {
55               algo = "sha1";
56           };
57       };
58   };
59
60.. code-block:: python
61
62   # Details regarding a file that may be read from a TFTP server. This variable
63   # may be omitted or set to None if PXE testing is not possible or desired.
64   env__net_pxe_bootable_file = {
65       'fn': 'default',
66       'addr': 0x10000000,
67       'size': 74,
68       'timeout': 50000,
69       'pattern': 'Linux',
70       'valid_label': '1',
71       'invalid_label': '2',
72       'exp_str_invalid': 'Skipping install for failure retrieving',
73       'local_label': '3',
74       'exp_str_local': 'missing environment variable: localcmd',
75       'empty_label': '4',
76       'exp_str_empty': 'No kernel given, skipping boot',
77       'check_type': 'boot_error',
78       'check_pattern': 'ERROR',
79   }
80
81   # False if a PXE boot test should be tested.
82   # If PXE boot testing is not possible or desired, set this variable to True.
83   # For example: If pxe configuration file is not proper to boot
84   env__pxe_boot_test_skip = False
85
86Here is the example of pxe configuration file ordered based on the execution
87flow:
88
891) /tftpboot/pxelinux.cfg/default-arm-zynqmp
90
91.. code-block::
92
93    menu include pxelinux.cfg/default-arm
94    timeout 50
95
96    default Linux
97
982) /tftpboot/pxelinux.cfg/default-arm
99
100.. code-block::
101
102    menu title Linux boot selections
103    menu include pxelinux.cfg/default
104
105    label install
106        menu label Invalid boot
107        kernel kernels/install.bin
108        append console=ttyAMA0,38400 debug earlyprintk
109        initrd initrds/uzInitrdDebInstall
110
111    label local
112        menu label Local boot
113        append root=/dev/sdb1
114        localboot 1
115
116    label boot
117        menu label Empty boot
118
1193) /tftpboot/pxelinux.cfg/default
120
121.. code-block::
122
123    label Linux
124        menu label Boot kernel
125        kernel Image
126        fdt system.dtb
127        initrd rootfs.cpio.gz.u-boot
128"""
129
130import pytest
131import utils
132import test_net
133import re
134
135def setup_networking(ubman):
136    """Setup networking
137
138    Making use of the test_net test, first try and configure networking via
139    DHCP. If this fails, fall back to static configuration.
140    """
141    test_net.test_net_dhcp(ubman)
142    if not test_net.net_set_up:
143        test_net.test_net_setup_static(ubman)
144
145def setup_tftpboot_boot(ubman):
146    """Setup for the tftpboot 'boot' test
147
148    We check that a file to use has been configured. If it has, we download it
149    and ensure it has the expected crc32 value.
150    """
151    f = ubman.config.env.get('env__net_tftp_bootable_file', None)
152    if not f:
153        pytest.skip('No TFTP bootable file to read')
154
155    setup_networking(ubman)
156    addr = f.get('addr', None)
157    if not addr:
158        addr = utils.find_ram_base(ubman)
159
160    fn = f['fn']
161    timeout = f.get('timeout', 50000)
162
163    with ubman.temporary_timeout(timeout):
164        output = ubman.run_command('tftpboot %x %s' % (addr, fn))
165
166    expected_text = 'Bytes transferred = '
167    sz = f.get('size', None)
168    if sz:
169        expected_text += '%d' % sz
170    assert expected_text in output
171
172    expected_crc = f.get('crc32', None)
173    output = ubman.run_command('crc32 %x $filesize' % addr)
174    if expected_crc:
175        assert expected_crc in output
176
177    pattern = f.get('pattern')
178    chk_type = f.get('check_type', 'boot_error')
179    chk_pattern = re.compile(f.get('check_pattern', 'ERROR'))
180    config = f.get('config', None)
181
182    return addr, timeout, pattern, chk_type, chk_pattern, config
183
184@pytest.mark.buildconfigspec('cmd_tftpboot')
185def test_net_tftpboot_boot(ubman):
186    """Boot the loaded image
187
188    A boot file (fit image) is downloaded from the TFTP server and booted using
189    bootm command with the default fit configuration, its boot log pattern are
190    validated.
191
192    The details of the file to download are provided by the boardenv_* file;
193    see the comment at the beginning of this file.
194    """
195    if ubman.config.env.get('env__tftp_boot_test_skip', True):
196        pytest.skip('TFTP boot test is not enabled!')
197
198    addr, timeout, pattern, chk_type, chk_pattern, imcfg = setup_tftpboot_boot(
199        ubman
200    )
201
202    if imcfg:
203        bootcmd = 'bootm %x#%s' % (addr, imcfg)
204    else:
205        bootcmd = 'bootm %x' % addr
206
207    with ubman.enable_check(
208        chk_type, chk_pattern
209    ), ubman.temporary_timeout(timeout):
210        try:
211            # wait_for_prompt=False makes the core code not wait for the U-Boot
212            # prompt code to be seen, since it won't be on a successful kernel
213            # boot
214            ubman.run_command(bootcmd, wait_for_prompt=False)
215
216            # Wait for boot log pattern
217            ubman.wait_for(pattern)
218        finally:
219            # This forces the console object to be shutdown, so any subsequent
220            # test will reset the board back into U-Boot. We want to force this
221            # no matter whether the kernel boot passed or failed.
222            ubman.drain_console()
223            ubman.cleanup_spawn()
224
225def setup_pxe_boot(ubman):
226    """Setup for the PXE 'boot' test
227
228    Make sure that the file to load via PXE boot has been configured.
229    """
230    f = ubman.config.env.get('env__net_pxe_bootable_file', None)
231    if not f:
232        pytest.skip('No PXE bootable file to read')
233
234    setup_networking(ubman)
235    bootfile = ubman.run_command('echo $bootfile')
236    if not bootfile:
237        bootfile = '<NULL>'
238
239    return f, bootfile
240
241@pytest.mark.buildconfigspec('cmd_pxe')
242def test_net_pxe_boot(ubman):
243    """Test the pxe boot command.
244
245    A pxe configuration file is downloaded from the TFTP server and interpreted
246    to boot the images mentioned in pxe configuration file.
247
248    The details of the file to download are provided by the boardenv_* file;
249    see the comment at the beginning of this file.
250    """
251    if ubman.config.env.get('env__pxe_boot_test_skip', True):
252        pytest.skip('PXE boot test is not enabled!')
253
254    f, bootfile = setup_pxe_boot(ubman)
255    addr = f.get('addr', None)
256    timeout = f.get('timeout', ubman.p.timeout)
257    fn = f['fn']
258
259    if addr:
260        ubman.run_command('setenv pxefile_addr_r %x' % addr)
261
262    with ubman.temporary_timeout(timeout):
263        output = ubman.run_command('pxe get')
264
265    expected_text = 'Bytes transferred = '
266    sz = f.get('size', None)
267    if sz:
268        expected_text += '%d' % sz
269    assert 'TIMEOUT' not in output
270    assert expected_text in output
271    assert f"Config file '{bootfile}' found" in output
272
273    pattern = f.get('pattern')
274    chk_type = f.get('check_type', 'boot_error')
275    chk_pattern = re.compile(f.get('check_pattern', 'ERROR'))
276
277    if not addr:
278        pxe_boot_cmd = 'pxe boot'
279    else:
280        pxe_boot_cmd = 'pxe boot %x' % addr
281
282    with ubman.enable_check(
283        chk_type, chk_pattern
284    ), ubman.temporary_timeout(timeout):
285        try:
286            ubman.run_command(pxe_boot_cmd, wait_for_prompt=False)
287            ubman.wait_for(pattern)
288        finally:
289            ubman.drain_console()
290            ubman.cleanup_spawn()
291
292@pytest.mark.buildconfigspec('cmd_pxe')
293def test_net_pxe_boot_config(ubman):
294    """Test the pxe boot command by selecting different combination of labels
295
296    A pxe configuration file is downloaded from the TFTP server and interpreted
297    to boot the images mentioned in pxe configuration file.
298
299    The details of the file to download are provided by the boardenv_* file;
300    see the comment at the beginning of this file.
301    """
302    if ubman.config.env.get('env__pxe_boot_test_skip', True):
303        pytest.skip('PXE boot test is not enabled!')
304
305    f, bootfile = setup_pxe_boot(ubman)
306    addr = f.get('addr', None)
307    timeout = f.get('timeout', ubman.p.timeout)
308    fn = f['fn']
309    local_label = f['local_label']
310    empty_label = f['empty_label']
311    exp_str_local = f['exp_str_local']
312    exp_str_empty = f['exp_str_empty']
313
314    if addr:
315        ubman.run_command('setenv pxefile_addr_r %x' % addr)
316
317    with ubman.temporary_timeout(timeout):
318        output = ubman.run_command('pxe get')
319
320    expected_text = 'Bytes transferred = '
321    sz = f.get('size', None)
322    if sz:
323        expected_text += '%d' % sz
324    assert 'TIMEOUT' not in output
325    assert expected_text in output
326    assert f"Config file '{bootfile}' found" in output
327
328    pattern = f.get('pattern')
329    chk_type = f.get('check_type', 'boot_error')
330    chk_pattern = re.compile(f.get('check_pattern', 'ERROR'))
331
332    if not addr:
333        pxe_boot_cmd = 'pxe boot'
334    else:
335        pxe_boot_cmd = 'pxe boot %x' % addr
336
337    with ubman.enable_check(
338        chk_type, chk_pattern
339    ), ubman.temporary_timeout(timeout):
340        try:
341            ubman.run_command(pxe_boot_cmd, wait_for_prompt=False)
342
343            # pxe config is loaded where multiple labels are there and need to
344            # select particular label to boot and check for expected string
345            # In this case, local label is selected and it should look for
346            # localcmd env variable and if that variable is not defined it
347            # should not boot it and come out to u-boot prompt
348            ubman.wait_for('Enter choice:')
349            ubman.run_command(local_label, wait_for_prompt=False)
350            expected_str = ubman.p.expect([exp_str_local])
351            assert (
352                expected_str == 0
353            ), f'Expected string: {exp_str_local} did not match!'
354
355            # In this case, empty label is selected and it should look for
356            # kernel image path and if it is not set it should fail it and load
357            # default label to boot
358            ubman.run_command(pxe_boot_cmd, wait_for_prompt=False)
359            ubman.wait_for('Enter choice:')
360            ubman.run_command(empty_label, wait_for_prompt=False)
361            expected_str = ubman.p.expect([exp_str_empty])
362            assert (
363                expected_str == 0
364            ), f'Expected string: {exp_str_empty} did not match!'
365
366            ubman.wait_for(pattern)
367        finally:
368            ubman.drain_console()
369            ubman.cleanup_spawn()
370
371@pytest.mark.buildconfigspec('cmd_pxe')
372def test_net_pxe_boot_config_invalid(ubman):
373    """Test the pxe boot command by selecting invalid label
374
375    A pxe configuration file is downloaded from the TFTP server and interpreted
376    to boot the images mentioned in pxe configuration file.
377
378    The details of the file to download are provided by the boardenv_* file;
379    see the comment at the beginning of this file.
380    """
381    if ubman.config.env.get('env__pxe_boot_test_skip', True):
382        pytest.skip('PXE boot test is not enabled!')
383
384    f, bootfile = setup_pxe_boot(ubman)
385    addr = f.get('addr', None)
386    timeout = f.get('timeout', ubman.p.timeout)
387    fn = f['fn']
388    invalid_label = f['invalid_label']
389    exp_str_invalid = f['exp_str_invalid']
390
391    if addr:
392        ubman.run_command('setenv pxefile_addr_r %x' % addr)
393
394    with ubman.temporary_timeout(timeout):
395        output = ubman.run_command('pxe get')
396
397    expected_text = 'Bytes transferred = '
398    sz = f.get('size', None)
399    if sz:
400        expected_text += '%d' % sz
401    assert 'TIMEOUT' not in output
402    assert expected_text in output
403    assert f"Config file '{bootfile}' found" in output
404
405    pattern = f.get('pattern')
406    if not addr:
407        pxe_boot_cmd = 'pxe boot'
408    else:
409        pxe_boot_cmd = 'pxe boot %x' % addr
410
411    with ubman.temporary_timeout(timeout):
412        try:
413            ubman.run_command(pxe_boot_cmd, wait_for_prompt=False)
414
415            # pxe config is loaded where multiple labels are there and need to
416            # select particular label to boot and check for expected string
417            # In this case invalid label is selected, it should load invalid
418            # label and if it fails it should load the default label to boot
419            ubman.wait_for('Enter choice:')
420            ubman.run_command(invalid_label, wait_for_prompt=False)
421            expected_str = ubman.p.expect([exp_str_invalid])
422            assert (
423                expected_str == 0
424            ), f'Expected string: {exp_str_invalid} did not match!'
425
426            ubman.wait_for(pattern)
427        finally:
428            ubman.drain_console()
429            ubman.cleanup_spawn()
430