1# SPDX-License-Identifier:      GPL-2.0+
2""" Unit test for UEFI menu-driven configuration
3"""
4
5import pytest
6import shutil
7import pytest
8import time
9from subprocess import call, check_call, CalledProcessError
10from tests import fs_helper
11
12@pytest.mark.boardspec('sandbox')
13@pytest.mark.buildconfigspec('cmd_eficonfig')
14@pytest.mark.buildconfigspec('cmd_bootefi_bootmgr')
15def test_efi_eficonfig(ubman):
16
17    def prepare_image(u_boot_config):
18        """Set up a file system to be used in UEFI "eficonfig" command
19           tests. This creates a disk image with the following files:
20                 initrd-1.img
21                 initrd-2.img
22                 initrddump.efi
23
24        Args:
25            u_boot_config -- U-Boot configuration.
26
27        Return:
28            A path to disk image to be used for testing
29
30        """
31        try:
32            image_path, mnt_point = fs_helper.setup_image(u_boot_config, 0,
33                                                          0xc,
34                                                          basename='test_eficonfig')
35
36            with open(mnt_point + '/initrd-1.img', 'w', encoding = 'ascii') as file:
37                file.write("initrd 1")
38
39            with open(mnt_point + '/initrd-2.img', 'w', encoding = 'ascii') as file:
40                file.write("initrd 2")
41
42            shutil.copyfile(u_boot_config.build_dir + '/lib/efi_loader/initrddump.efi',
43                            mnt_point + '/initrddump.efi')
44
45            fsfile = fs_helper.mk_fs(ubman.config, 'vfat', 0x100000,
46                                     'test_eficonfig', mnt_point)
47            check_call(f'dd if={fsfile} of={image_path} bs=1M seek=1', shell=True)
48
49            yield image_path
50        except CalledProcessError as err:
51            pytest.skip('Preparing test_eficonfig image failed')
52            call('rm -f %s' % image_path, shell=True)
53        finally:
54            call('rm -rf %s' % mnt_point, shell=True)
55            call('rm -f %s' % image_path, shell=True)
56
57    def send_user_input_and_wait(user_str, expect_str):
58        time.sleep(0.1) # TODO: does not work correctly without sleep
59        ubman.run_command(cmd=user_str, wait_for_prompt=False,
60                                   wait_for_echo=True, send_nl=False)
61        ubman.run_command(cmd='\x0d', wait_for_prompt=False,
62                                   wait_for_echo=False, send_nl=False)
63        if expect_str is not None:
64            for i in expect_str:
65                ubman.p.expect([i])
66
67    def press_up_down_enter_and_wait(up_count, down_count, enter, expect_str):
68        # press UP key
69        for i in range(up_count):
70            ubman.run_command(cmd='\x1b\x5b\x41', wait_for_prompt=False,
71                                       wait_for_echo=False, send_nl=False)
72        # press DOWN key
73        for i in range(down_count):
74            ubman.run_command(cmd='\x1b\x5b\x42', wait_for_prompt=False,
75                                       wait_for_echo=False, send_nl=False)
76        # press ENTER if requested
77        if enter:
78            ubman.run_command(cmd='\x0d', wait_for_prompt=False,
79                                       wait_for_echo=False, send_nl=False)
80        # wait expected output
81        if expect_str is not None:
82            for i in expect_str:
83                ubman.p.expect([i])
84
85    def press_escape_key(wait_prompt):
86        ubman.run_command(cmd='\x1b', wait_for_prompt=wait_prompt, wait_for_echo=False, send_nl=False)
87
88    def press_enter_key(wait_prompt):
89        ubman.run_command(cmd='\x0d', wait_for_prompt=wait_prompt,
90                                   wait_for_echo=False, send_nl=False)
91
92    def check_current_is_maintenance_menu():
93        for i in ('UEFI Maintenance Menu', 'Add Boot Option', 'Edit Boot Option',
94                  'Change Boot Order', 'Delete Boot Option', 'Quit'):
95            ubman.p.expect([i])
96
97    """ Unit test for "eficonfig" command
98    The menu-driven interface is used to set up UEFI load options.
99    The bootefi bootmgr loads initrddump.efi as a payload.
100    The crc32 of the loaded initrd.img is checked
101
102    Args:
103        ubman -- U-Boot console
104    """
105    # This test passes for unknown reasons in the bowels of U-Boot. It needs to
106    # be replaced with a unit test.
107    return
108
109    # Restart the system to clean the previous state
110    ubman.restart_uboot()
111
112    efi_eficonfig_data = prepare_image(ubman.config)
113    with ubman.temporary_timeout(500):
114        #
115        # Test Case 1: Check the menu is displayed
116        #
117        ubman.run_command('eficonfig', wait_for_prompt=False)
118        for i in ('UEFI Maintenance Menu', 'Add Boot Option', 'Edit Boot Option',
119                  'Change Boot Order', 'Delete Boot Option', 'Quit'):
120            ubman.p.expect([i])
121        # Select "Add Boot Option"
122        press_enter_key(False)
123        for i in ('Add Boot Option', 'Description:', 'File', 'Initrd File', 'Optional Data',
124                  'Save', 'Quit'):
125            ubman.p.expect([i])
126        press_escape_key(False)
127        check_current_is_maintenance_menu()
128        # return to U-Boot console
129        press_escape_key(True)
130
131        #
132        # Test Case 2: check auto generated media device entry
133        #
134
135        # bind the test disk image for succeeding tests
136        ubman.run_command(cmd = f'host bind 0 {efi_eficonfig_data}')
137
138        ubman.run_command('eficonfig', wait_for_prompt=False)
139
140        # Change the Boot Order
141        press_up_down_enter_and_wait(0, 2, True, 'Quit')
142        for i in ('host 0:1', 'Save', 'Quit'):
143            ubman.p.expect([i])
144        # disable auto generated boot option for succeeding test
145        ubman.run_command(cmd=' ', wait_for_prompt=False,
146                                       wait_for_echo=False, send_nl=False)
147        # Save the BootOrder
148        press_up_down_enter_and_wait(0, 1, True, None)
149        check_current_is_maintenance_menu()
150
151        #
152        # Test Case 3: Add first Boot Option and load it
153        #
154
155        # Select 'Add Boot Option'
156        press_up_down_enter_and_wait(0, 0, True, 'Quit')
157
158        # Press the enter key to select 'Description:' entry, then enter Description
159        press_up_down_enter_and_wait(0, 0, True, 'Enter description:')
160        # Send Description user input, press ENTER key to complete
161        send_user_input_and_wait('test 1', 'Quit')
162
163        # Set EFI image(initrddump.efi)
164        press_up_down_enter_and_wait(0, 1, True, 'Quit')
165        press_up_down_enter_and_wait(0, 0, True, 'host 0:1')
166        # Select 'host 0:1'
167        press_up_down_enter_and_wait(0, 0, True, 'Quit')
168        # Press down key to select "initrddump.efi" entry followed by the enter key
169        press_up_down_enter_and_wait(0, 2, True, 'Quit')
170
171        # Set Initrd file(initrd-1.img)
172        press_up_down_enter_and_wait(0, 2, True, 'Quit')
173        press_up_down_enter_and_wait(0, 0, True, 'host 0:1')
174        # Select 'host 0:1'
175        press_up_down_enter_and_wait(0, 0, True, 'Quit')
176        # Press down key to select "initrd-1.img" entry followed by the enter key
177        press_up_down_enter_and_wait(0, 0, True, 'Quit')
178
179        # Set optional_data
180        press_up_down_enter_and_wait(0, 3, True, 'Optional Data:')
181        # Send Description user input, press ENTER key to complete
182        send_user_input_and_wait('nocolor', None)
183        for i in ('Description: test 1', 'File: host 0:1/initrddump.efi',
184                  'Initrd File: host 0:1/initrd-1.img', 'Optional Data: nocolor', 'Save', 'Quit'):
185            ubman.p.expect([i])
186
187        # Save the Boot Option
188        press_up_down_enter_and_wait(0, 4, True, None)
189        check_current_is_maintenance_menu()
190
191        # Check the newly added Boot Option is handled correctly
192        # Return to U-Boot console
193        press_escape_key(True)
194        ubman.run_command(cmd = 'bootefi bootmgr')
195        response = ubman.run_command(cmd = 'load', wait_for_echo=False)
196        assert 'crc32: 0x181464af' in response
197        ubman.run_command(cmd = 'exit', wait_for_echo=False)
198
199        #
200        # Test Case 4: Add second Boot Option and load it
201        #
202        ubman.run_command('eficonfig', wait_for_prompt=False)
203
204        # Select 'Add Boot Option'
205        press_up_down_enter_and_wait(0, 0, True, 'Quit')
206
207        # Press the enter key to select 'Description:' entry, then enter Description
208        press_up_down_enter_and_wait(0, 0, True, 'Enter description:')
209        # Send Description user input, press ENTER key to complete
210        send_user_input_and_wait('test 2', 'Quit')
211
212        # Set EFI image(initrddump.efi)
213        press_up_down_enter_and_wait(0, 1, True, 'Quit')
214        press_up_down_enter_and_wait(0, 0, True, 'host 0:1')
215        # Select 'host 0:1'
216        press_up_down_enter_and_wait(0, 0, True, 'Quit')
217        # Press down key to select "initrddump.efi" entry followed by the enter key
218        press_up_down_enter_and_wait(0, 2, True, 'Quit')
219
220        # Set Initrd file(initrd-2.img)
221        press_up_down_enter_and_wait(0, 2, True, 'Quit')
222        press_up_down_enter_and_wait(0, 0, True, 'host 0:1')
223        # Select 'host 0:1'
224        press_up_down_enter_and_wait(0, 0, True, 'Quit')
225        # Press down key to select "initrd-2.img" entry followed by the enter key
226        press_up_down_enter_and_wait(0, 1, True, 'Quit')
227
228        # Set optional_data
229        press_up_down_enter_and_wait(0, 3, True, 'Optional Data:')
230        # Send Description user input, press ENTER key to complete
231        send_user_input_and_wait('nocolor', None)
232        for i in ('Description: test 2', 'File: host 0:1/initrddump.efi',
233                  'Initrd File: host 0:1/initrd-2.img', 'Optional Data: nocolor', 'Save', 'Quit'):
234            ubman.p.expect([i])
235
236        # Save the Boot Option
237        press_up_down_enter_and_wait(0, 4, True, 'Quit')
238
239        # Change the Boot Order
240        press_up_down_enter_and_wait(0, 2, True, 'Quit')
241        press_up_down_enter_and_wait(0, 1, False, 'Quit')
242        # move 'test 1' to the second entry
243        ubman.run_command(cmd='+', wait_for_prompt=False,
244                                       wait_for_echo=False, send_nl=False)
245        for i in ('test 2', 'test 1', 'host 0:1', 'Save', 'Quit'):
246            ubman.p.expect([i])
247        # Save the BootOrder
248        press_up_down_enter_and_wait(0, 3, True, None)
249        check_current_is_maintenance_menu()
250
251        # Check the newly added Boot Option is handled correctly
252        # Return to U-Boot console
253        press_escape_key(True)
254        ubman.run_command(cmd = 'bootefi bootmgr')
255        response = ubman.run_command(cmd = 'load', wait_for_echo=False)
256        assert 'crc32: 0x811d3515' in response
257        ubman.run_command(cmd = 'exit', wait_for_echo=False)
258
259        #
260        # Test Case 5: Change BootOrder and load it
261        #
262        ubman.run_command('eficonfig', wait_for_prompt=False)
263
264        # Change the Boot Order
265        press_up_down_enter_and_wait(0, 2, True, None)
266        # Check the current BootOrder
267        for i in ('test 2', 'test 1', 'host 0:1', 'Save', 'Quit'):
268            ubman.p.expect([i])
269        # move 'test 2' to the second entry
270        ubman.run_command(cmd='-', wait_for_prompt=False,
271                                       wait_for_echo=False, send_nl=False)
272        for i in ('test 1', 'test 2', 'host 0:1', 'Save', 'Quit'):
273            ubman.p.expect([i])
274        # Save the BootOrder
275        press_up_down_enter_and_wait(0, 2, True, None)
276        check_current_is_maintenance_menu()
277
278        # Return to U-Boot console
279        press_escape_key(True)
280        ubman.run_command(cmd = 'bootefi bootmgr')
281        response = ubman.run_command(cmd = 'load', wait_for_echo=False)
282        assert 'crc32: 0x181464af' in response
283        ubman.run_command(cmd = 'exit', wait_for_echo=False)
284
285        #
286        # Test Case 6: Delete Boot Option(label:test 2)
287        #
288        ubman.run_command('eficonfig', wait_for_prompt=False)
289
290        # Select 'Delete Boot Option'
291        press_up_down_enter_and_wait(0, 3, True, None)
292        # Check the current BootOrder
293        for i in ('test 1', 'test 2', 'Quit'):
294            ubman.p.expect([i])
295
296        # Delete 'test 2'
297        press_up_down_enter_and_wait(0, 1, True, None)
298        for i in ('test 1', 'Quit'):
299            ubman.p.expect([i])
300        press_escape_key(False)
301        check_current_is_maintenance_menu()
302        # Return to U-Boot console
303        press_escape_key(True)
304
305        #
306        # Test Case 7: Edit Boot Option
307        #
308        ubman.run_command('eficonfig', wait_for_prompt=False)
309        # Select 'Edit Boot Option'
310        press_up_down_enter_and_wait(0, 1, True, None)
311        # Check the current BootOrder
312        for i in ('test 1', 'Quit'):
313            ubman.p.expect([i])
314        press_up_down_enter_and_wait(0, 0, True, None)
315        for i in ('Description: test 1', 'File: host 0:1/initrddump.efi',
316                  'Initrd File: host 0:1/initrd-1.img', 'Optional Data: nocolor', 'Save', 'Quit'):
317            ubman.p.expect([i])
318
319        # Press the enter key to select 'Description:' entry, then enter Description
320        press_up_down_enter_and_wait(0, 0, True, 'Enter description:')
321        # Send Description user input, press ENTER key to complete
322        send_user_input_and_wait('test 3', 'Quit')
323
324        # Set EFI image(initrddump.efi)
325        press_up_down_enter_and_wait(0, 1, True, 'Quit')
326        press_up_down_enter_and_wait(0, 0, True, 'host 0:1')
327        # Select 'host 0:1'
328        press_up_down_enter_and_wait(0, 0, True, 'Quit')
329        # Press down key to select "initrddump.efi" entry followed by the enter key
330        press_up_down_enter_and_wait(0, 2, True, 'Quit')
331
332        # Set Initrd file(initrd-2.img)
333        press_up_down_enter_and_wait(0, 2, True, 'Quit')
334        press_up_down_enter_and_wait(0, 0, True, 'host 0:1')
335        # Select 'host 0:1'
336        press_up_down_enter_and_wait(0, 0, True, 'Quit')
337        # Press down key to select "initrd-1.img" entry followed by the enter key
338        press_up_down_enter_and_wait(0, 1, True, 'Quit')
339
340        # Set optional_data
341        press_up_down_enter_and_wait(0, 3, True, 'Optional Data:')
342        # Send Description user input, press ENTER key to complete
343        send_user_input_and_wait('', None)
344        for i in ('Description: test 3', 'File: host 0:1/initrddump.efi',
345                  'Initrd File: host 0:1/initrd-2.img', 'Optional Data:', 'Save', 'Quit'):
346            ubman.p.expect([i])
347
348        # Save the Boot Option
349        press_up_down_enter_and_wait(0, 4, True, 'Quit')
350        press_escape_key(False)
351        check_current_is_maintenance_menu()
352
353        # Check the updated Boot Option is handled correctly
354        # Return to U-Boot console
355        press_escape_key(True)
356        ubman.run_command(cmd = 'bootefi bootmgr')
357        response = ubman.run_command(cmd = 'load', wait_for_echo=False)
358        assert 'crc32: 0x811d3515' in response
359        ubman.run_command(cmd = 'exit', wait_for_echo=False)
360
361        #
362        # Test Case 8: Delete Boot Option(label:test 3)
363        #
364        ubman.run_command('eficonfig', wait_for_prompt=False)
365
366        # Select 'Delete Boot Option'
367        press_up_down_enter_and_wait(0, 3, True, None)
368        # Check the current BootOrder
369        for i in ('test 3', 'Quit'):
370            ubman.p.expect([i])
371
372        # Delete 'test 3'
373        press_up_down_enter_and_wait(0, 0, True, 'Quit')
374        press_escape_key(False)
375        check_current_is_maintenance_menu()
376        # Return to U-Boot console
377        press_escape_key(True)
378
379        # remove the host device
380        ubman.run_command(cmd = f'host bind -r 0')
381
382        #
383        # Test Case 9: No block device found
384        #
385        ubman.run_command('eficonfig', wait_for_prompt=False)
386
387        # Select 'Add Boot Option'
388        press_up_down_enter_and_wait(0, 0, True, 'Quit')
389
390        # Set EFI image
391        press_up_down_enter_and_wait(0, 1, True, 'Quit')
392        press_up_down_enter_and_wait(0, 0, True, 'No block device found!')
393        press_escape_key(False)
394        press_escape_key(False)
395        check_current_is_maintenance_menu()
396        # Return to U-Boot console
397        press_escape_key(True)
398