1# SPDX-License-Identifier: GPL-2.0
2# (C) Copyright 2023, Advanced Micro Devices, Inc.
3
4import pytest
5import random
6import string
7import test_net
8
9"""
10Note: This test relies on boardenv_* containing configuration values to define
11RPU applications information for AMD's ZynqMP SoC which contains, application
12names, processors, address where it is built, expected output and the tftp load
13addresses. This test will be automatically skipped without this.
14
15It also relies on dhcp or setup_static net test to support tftp to load
16application on DDR. All the environment parameters are stored sequentially.
17The length of all parameters values should be same. For example, if 2 app_names
18are defined in a list as a value of parameter 'app_name' then the other
19parameters value also should have a list with 2 items.
20It will run RPU cases for all the applications defined in boardenv_*
21configuration file.
22
23Example:
24env__zynqmp_rpu_apps = {
25    'app_name': ['hello_world_r5_0_ddr.elf', 'hello_world_r5_1_ddr.elf'],
26    'proc': ['rpu0', 'rpu1'],
27    'cpu_num': [4, 5],
28    'addr': [0xA00000, 0xB00000],
29    'output': ['Successfully ran Hello World application on DDR from RPU0',
30               'Successfully ran Hello World application on DDR from RPU1'],
31    'tftp_addr': [0x100000, 0x200000],
32}
33"""
34
35# Get rpu apps params from env
36def get_rpu_apps_env(u_boot_console):
37    rpu_apps = u_boot_console.config.env.get('env__zynqmp_rpu_apps', False)
38    if not rpu_apps:
39        pytest.skip('ZynqMP RPU application info not defined!')
40
41    apps = rpu_apps.get('app_name', None)
42    if not apps:
43        pytest.skip('No RPU application found!')
44
45    procs = rpu_apps.get('proc', None)
46    if not procs:
47        pytest.skip('No RPU application processor provided!')
48
49    cpu_nums = rpu_apps.get('cpu_num', None)
50    if not cpu_nums:
51        pytest.skip('No CPU number for respective processor provided!')
52
53    addrs = rpu_apps.get('addr', None)
54    if not addrs:
55        pytest.skip('No RPU application build address found!')
56
57    outputs = rpu_apps.get('output', None)
58    if not outputs:
59        pytest.skip('Expected output not found!')
60
61    tftp_addrs = rpu_apps.get('tftp_addr', None)
62    if not tftp_addrs:
63        pytest.skip('TFTP address to load application not found!')
64
65    return apps, procs, cpu_nums, addrs, outputs, tftp_addrs
66
67# Check return code
68def ret_code(u_boot_console):
69    return u_boot_console.run_command('echo $?')
70
71# Initialize tcm
72def tcminit(u_boot_console, rpu_mode):
73    output = u_boot_console.run_command('zynqmp tcminit %s' % rpu_mode)
74    assert 'Initializing TCM overwrites TCM content' in output
75    return ret_code(u_boot_console)
76
77# Load application in DDR
78def load_app_ddr(u_boot_console, tftp_addr, app):
79    output = u_boot_console.run_command('tftpboot %x %s' % (tftp_addr, app))
80    assert 'TIMEOUT' not in output
81    assert 'Bytes transferred = ' in output
82
83    # Load elf
84    u_boot_console.run_command('bootelf -p %x' % tftp_addr)
85    assert ret_code(u_boot_console).endswith('0')
86
87# Disable cpus
88def disable_cpus(u_boot_console, cpu_nums):
89    for num in cpu_nums:
90        u_boot_console.run_command(f'cpu {num} disable')
91
92# Load apps on RPU cores
93def rpu_apps_load(u_boot_console, rpu_mode):
94    apps, procs, cpu_nums, addrs, outputs, tftp_addrs = get_rpu_apps_env(
95        u_boot_console)
96    test_net.test_net_dhcp(u_boot_console)
97    if not test_net.net_set_up:
98        test_net.test_net_setup_static(u_boot_console)
99
100    try:
101        assert tcminit(u_boot_console, rpu_mode).endswith('0')
102
103        for i in range(len(apps)):
104            if rpu_mode == 'lockstep' and procs[i] != 'rpu0':
105                continue
106
107            load_app_ddr(u_boot_console, tftp_addrs[i], apps[i])
108            rel_addr = int(addrs[i] + 0x3C)
109
110            # Release cpu at app load address
111            cpu_num = cpu_nums[i]
112            cmd = 'cpu %d release %x %s' % (cpu_num, rel_addr, rpu_mode)
113            output = u_boot_console.run_command(cmd)
114            exp_op = f'Using TCM jump trampoline for address {hex(rel_addr)}'
115            assert exp_op in output
116            assert f'R5 {rpu_mode} mode' in output
117            u_boot_console.wait_for(outputs[i])
118            assert ret_code(u_boot_console).endswith('0')
119    finally:
120        disable_cpus(u_boot_console, cpu_nums)
121
122@pytest.mark.buildconfigspec('cmd_zynqmp')
123def test_zynqmp_rpu_app_load_split(u_boot_console):
124    rpu_apps_load(u_boot_console, 'split')
125
126@pytest.mark.buildconfigspec('cmd_zynqmp')
127def test_zynqmp_rpu_app_load_lockstep(u_boot_console):
128    rpu_apps_load(u_boot_console, 'lockstep')
129
130@pytest.mark.buildconfigspec('cmd_zynqmp')
131def test_zynqmp_rpu_app_load_negative(u_boot_console):
132    apps, procs, cpu_nums, addrs, outputs, tftp_addrs = get_rpu_apps_env(
133        u_boot_console)
134
135    # Invalid commands
136    u_boot_console.run_command('zynqmp tcminit mode')
137    assert ret_code(u_boot_console).endswith('1')
138
139    rand_str = ''.join(random.choices(string.ascii_lowercase, k=4))
140    u_boot_console.run_command('zynqmp tcminit %s' % rand_str)
141    assert ret_code(u_boot_console).endswith('1')
142
143    rand_num = random.randint(2, 100)
144    u_boot_console.run_command('zynqmp tcminit %d' % rand_num)
145    assert ret_code(u_boot_console).endswith('1')
146
147    test_net.test_net_dhcp(u_boot_console)
148    if not test_net.net_set_up:
149        test_net.test_net_setup_static(u_boot_console)
150
151    try:
152        rpu_mode = 'split'
153        assert tcminit(u_boot_console, rpu_mode).endswith('0')
154
155        for i in range(len(apps)):
156            load_app_ddr(u_boot_console, tftp_addrs[i], apps[i])
157
158            # Run in split mode at different load address
159            rel_addr = int(addrs[i]) + random.randint(200, 1000)
160            cpu_num = cpu_nums[i]
161            cmd = 'cpu %d release %x %s' % (cpu_num, rel_addr, rpu_mode)
162            output = u_boot_console.run_command(cmd)
163            exp_op = f'Using TCM jump trampoline for address {hex(rel_addr)}'
164            assert exp_op in output
165            assert f'R5 {rpu_mode} mode' in output
166            assert not outputs[i] in output
167
168            # Invalid rpu mode
169            rand_str = ''.join(random.choices(string.ascii_lowercase, k=4))
170            cmd = 'cpu %d release %x %s' % (cpu_num, rel_addr, rand_str)
171            output = u_boot_console.run_command(cmd)
172            assert exp_op in output
173            assert f'Unsupported mode' in output
174            assert not ret_code(u_boot_console).endswith('0')
175
176        # Switch to lockstep mode, without disabling CPUs
177        rpu_mode = 'lockstep'
178        u_boot_console.run_command('zynqmp tcminit %s' % rpu_mode)
179        assert not ret_code(u_boot_console).endswith('0')
180
181        # Disable cpus
182        disable_cpus(u_boot_console, cpu_nums)
183
184        # Switch to lockstep mode, after disabling CPUs
185        output = u_boot_console.run_command('zynqmp tcminit %s' % rpu_mode)
186        assert 'Initializing TCM overwrites TCM content' in output
187        assert ret_code(u_boot_console).endswith('0')
188
189        # Run lockstep mode for RPU1
190        for i in range(len(apps)):
191            if procs[i] == 'rpu0':
192                continue
193
194            load_app_ddr(u_boot_console, tftp_addrs[i], apps[i])
195            rel_addr = int(addrs[i] + 0x3C)
196            cpu_num = cpu_nums[i]
197            cmd = 'cpu %d release %x %s' % (cpu_num, rel_addr, rpu_mode)
198            output = u_boot_console.run_command(cmd)
199            exp_op = f'Using TCM jump trampoline for address {hex(rel_addr)}'
200            assert exp_op in output
201            assert f'R5 {rpu_mode} mode' in output
202            assert u_boot_console.p.expect([outputs[i]])
203    finally:
204        disable_cpus(u_boot_console, cpu_nums)
205        # This forces the console object to be shutdown, so any subsequent test
206        # will reset the board back into U-Boot.
207        u_boot_console.drain_console()
208        u_boot_console.cleanup_spawn()
209