Merge ~waveform/ubuntu-release-upgrader:remove-uboot into ubuntu-release-upgrader:ubuntu/master

Proposed by Dave Jones
Status: Merged
Merged at revision: 36ed0aa86545ab59e44cb12eea07574f39f36d24
Proposed branch: ~waveform/ubuntu-release-upgrader:remove-uboot
Merge into: ubuntu-release-upgrader:ubuntu/master
Diff against target: 467 lines (+379/-34)
3 files modified
DistUpgrade/DistUpgradeQuirks.py (+137/-1)
debian/changelog (+7/-0)
tests/test_quirks.py (+235/-33)
Reviewer Review Type Date Requested Status
Brian Murray Needs Fixing
Review via email: mp+406663@code.launchpad.net

Commit message

Adds a quirk to remote u-boot from the boot sequence of Raspberry Pi installations on impish. This doesn't remove the u-boot binaries themselves, and leaves the actual configuration commented out in the config.txt in case user's wish to revert easily.

LP: #1936401

To post a comment you must log in.
Revision history for this message
Brian Murray (brian-murray) wrote :

I found the function _remove_uboot_on_rpi a bit hard to read and had to scroll up and down multiple times because the file is read, then some other functions are defined, and then the file is passed to those functions. It might be easier for others to read if that was restructured a bit.

I can't really comment on the correctness of the boot config contents but as a release upgrader quirk this seems good to me.

I've some additional comments in-line.

review: Needs Fixing
Revision history for this message
Dave Jones (waveform) wrote :

Added responses to each review comment. Will upload a commit to address the outstanding points.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/DistUpgrade/DistUpgradeQuirks.py b/DistUpgrade/DistUpgradeQuirks.py
2index 712c98e..9d6400b 100644
3--- a/DistUpgrade/DistUpgradeQuirks.py
4+++ b/DistUpgrade/DistUpgradeQuirks.py
5@@ -145,6 +145,9 @@ class DistUpgradeQuirks(object):
6 if 'ubuntu-desktop-raspi' in cache:
7 if cache['ubuntu-desktop-raspi'].is_installed:
8 self._replace_fkms_overlay()
9+ if 'linux-firmware-raspi2' in cache:
10+ if cache['linux-firmware-raspi2'].is_installed:
11+ self._remove_uboot_on_rpi()
12
13 # individual quirks handler when the dpkg run is finished ---------
14 def PostCleanup(self):
15@@ -1157,7 +1160,7 @@ class DistUpgradeQuirks(object):
16
17 def _replace_fkms_overlay(self, boot_dir='/boot/firmware'):
18 failure_action = (
19- "you may need to replace the vc4-fkms-v3d overlay with "
20+ "You may need to replace the vc4-fkms-v3d overlay with "
21 "vc4-kms-v3d in config.txt on your boot partition")
22
23 try:
24@@ -1205,3 +1208,136 @@ class DistUpgradeQuirks(object):
25 except IOError as exc:
26 logging.error("unable to write new boot config to %s: %s; %s",
27 boot_config_filename, exc, failure_action)
28+
29+ def _remove_uboot_on_rpi(self, boot_dir='/boot/firmware'):
30+ kernel_line = 'kernel=vmlinuz'
31+ initramfs_line = 'initramfs initrd.img followkernel'
32+ failure_action = (
33+ "You may need to replace u_boot_* with vmlinuz, and add "
34+ "{initramfs_line!r} to config.txt on your boot "
35+ "partition; see LP: #1936401 for further details")
36+ failure_action = failure_action.format(initramfs_line=initramfs_line)
37+ change_prefix = '# commented by do-release-upgrade (LP: #1936401)'
38+ added_prefix = '# added by do-release-upgrade (LP: #1936401)'
39+ merge_prefix = '# merged from {} by do-release-upgrade (LP: #1936401)'
40+
41+ try:
42+ boot_config_filename = os.path.join(boot_dir, 'config.txt')
43+ with open(boot_config_filename, 'r', encoding='utf-8') as f:
44+ boot_config = f.read()
45+ except FileNotFoundError:
46+ logging.error("failed to open boot configuration in %s; %s",
47+ boot_config_filename, failure_action)
48+ return
49+
50+ def replace_uboot(lines):
51+ result = []
52+ removed_uboot = added_kernel = False
53+ for line in lines:
54+ if line == '[all]':
55+ # Add the vmlinuz kernel and initrd.img to the first
56+ # encountered [all] section, just in case the user has any
57+ # custom kernel overrides later in the sequence
58+ result.append(line)
59+ if not added_kernel:
60+ result.append(added_prefix)
61+ result.append(kernel_line)
62+ result.append(initramfs_line)
63+ added_kernel = True
64+ elif line.startswith('device_tree_address='):
65+ # Disable any device_tree_address= line (leave the boot
66+ # firmware to place this rather than risk trampling over it
67+ # with kernel/initrd)
68+ result.append(change_prefix)
69+ result.append('#' + line)
70+ elif line.startswith('kernel=uboot_rpi_'):
71+ # Disable all kernel=uboot_rpi_* lines found in the
72+ # configuration
73+ removed_uboot = True
74+ result.append(change_prefix)
75+ result.append('#' + line)
76+ else:
77+ result.append(line)
78+ # We don't *want* to touch the config unless we absolutely have to,
79+ # and the user may have already performed this conversion (e.g. to
80+ # take advantage of USB MSD boot), or otherwise customized their
81+ # boot configuration. Hence, if we didn't find (and remove) any
82+ # u-boot entries, assume the kernel/initrd config was already fine
83+ # and return the verbatim config
84+ if removed_uboot:
85+ if not added_kernel:
86+ result.append(added_prefix)
87+ result.append('[all]')
88+ result.append(kernel_line)
89+ result.append(initramfs_line)
90+ return result
91+ else:
92+ return lines
93+
94+ def merge_includes(lines):
95+ result = []
96+ skip_comments = True
97+ found_includes = False
98+ for line in lines:
99+ # Skip the initial comment block warning that config.txt is not
100+ # to be edited (the usercfg.txt and syscfg.txt files were
101+ # merged in the groovy release, but not for upgraders)
102+ if line.startswith('#') and skip_comments:
103+ continue
104+ skip_comments = False
105+ if line in ('include syscfg.txt', 'include usercfg.txt'):
106+ # Merge the usercfg.txt and syscfg.txt configuration files
107+ # into config.txt, skipping the initial comments. Note that
108+ # we don't bother with other includes the user has added
109+ # themselves (as they presumably want to keep those)
110+ found_includes = True
111+ included_filename = line.split(maxsplit=1)[1]
112+ result.append(merge_prefix.format(included_filename))
113+ included_filename = os.path.join(boot_dir,
114+ included_filename)
115+ skip_comments = True
116+ with open(included_filename, 'r', encoding='utf-8') as f:
117+ for line in f:
118+ if line.startswith('#') and skip_comments:
119+ continue
120+ skip_comments = False
121+ result.append(line.rstrip())
122+ target_filename = included_filename + '.distUpgrade'
123+ try:
124+ os.rename(included_filename, target_filename)
125+ except IOError as exc:
126+ logging.error("failed to move included configuration "
127+ "from %s to %s; %s", included_filename,
128+ target_filename, exc)
129+ else:
130+ result.append(line)
131+ # Again, if we found no includes, return the original verbatim
132+ # (i.e. with initial comments intact)
133+ if found_includes:
134+ return result
135+ else:
136+ return lines
137+
138+ lines = [line.rstrip() for line in boot_config.splitlines()]
139+ lines = replace_uboot(merge_includes(lines))
140+ new_config = ''.join(line + '\n' for line in lines)
141+
142+ if new_config == boot_config:
143+ logging.warning("no u-boot removal performed in %s",
144+ boot_config_filename)
145+ return
146+
147+ try:
148+ boot_backup_filename = boot_config_filename + '.distUpgrade'
149+ with open(boot_backup_filename, 'w', encoding='utf-8') as f:
150+ f.write(boot_config)
151+ except IOError as exc:
152+ logging.error("unable to write boot config backup to %s: %s; %s",
153+ boot_backup_filename, exc, failure_action)
154+ return
155+ try:
156+ with open(boot_config_filename, 'w', encoding='utf-8') as f:
157+ f.write(new_config)
158+ except IOError as exc:
159+ logging.error("unable to write new boot config to %s: %s; %s",
160+ boot_config_filename, exc, failure_action)
161diff --git a/debian/changelog b/debian/changelog
162index 762e5e9..15d3ca0 100644
163--- a/debian/changelog
164+++ b/debian/changelog
165@@ -1,3 +1,10 @@
166+ubuntu-release-upgrader (1:21.10.7) impish; urgency=medium
167+
168+ * DistUpgrade/DistUpgradeQuirks.py: Add quirk to merge raspi boot config
169+ into one file, and remove u-boot references (LP: #1936401)
170+
171+ -- Dave Jones <dave.jones@canonical.com> Tue, 03 Aug 2021 14:50:36 +0100
172+
173 ubuntu-release-upgrader (1:21.10.6) impish; urgency=medium
174
175 * DistUpgrade/ReleaseAnnouncement, DistUpgrade/EOLReleaseAnnouncement:
176diff --git a/tests/test_quirks.py b/tests/test_quirks.py
177index cc4c8f1..2e96087 100644
178--- a/tests/test_quirks.py
179+++ b/tests/test_quirks.py
180@@ -458,13 +458,14 @@ class TestQuirks(unittest.TestCase):
181
182 def test_replace_fkms_overlay_no_changes(self):
183 with tempfile.TemporaryDirectory() as boot_dir:
184- demo_config = ("# This is a demo boot config\n"
185- "[pi4\n"
186- "max_framebuffers=2\n"
187- "[all]\n"
188- "arm_64bit=1\n"
189- "kernel=vmlinuz\n"
190- "initramfs initrd.img followkernel\n")
191+ demo_config = """\
192+# This is a demo boot config
193+[pi4]
194+max_framebuffers=2
195+[all]
196+arm_64bit=1
197+kernel=vmlinuz
198+initramfs initrd.img followkernel"""
199 with open(os.path.join(boot_dir, 'config.txt'), 'w') as f:
200 f.write(demo_config)
201
202@@ -480,32 +481,34 @@ class TestQuirks(unittest.TestCase):
203
204 def test_replace_fkms_overlay_with_changes(self):
205 with tempfile.TemporaryDirectory() as boot_dir:
206- demo_config = (
207- "# This is a demo boot config\n"
208- "[pi4\n"
209- "max_framebuffers=2\n"
210- "[all]\n"
211- "arm_64bit=1\n"
212- "kernel=vmlinuz\n"
213- "initramfs initrd.img followkernel\n"
214- "dtoverlay=vc4-fkms-v3d,cma-256\n"
215- "start_x=1\n"
216- "gpu_mem=256\n")
217- expected_config = (
218- "# This is a demo boot config\n"
219- "[pi4\n"
220- "max_framebuffers=2\n"
221- "[all]\n"
222- "arm_64bit=1\n"
223- "kernel=vmlinuz\n"
224- "initramfs initrd.img followkernel\n"
225- "# changed by do-release-upgrade (LP: #1923673)\n"
226- "#dtoverlay=vc4-fkms-v3d,cma-256\n"
227- "dtoverlay=vc4-kms-v3d,cma-256\n"
228- "# disabled by do-release-upgrade (LP: #1923673)\n"
229- "#start_x=1\n"
230- "# disabled by do-release-upgrade (LP: #1923673)\n"
231- "#gpu_mem=256\n")
232+ demo_config = """\
233+# This is a demo boot config
234+[pi4]
235+max_framebuffers=2
236+[all]
237+arm_64bit=1
238+kernel=vmlinuz
239+initramfs initrd.img followkernel
240+dtoverlay=vc4-fkms-v3d,cma-256
241+start_x=1
242+gpu_mem=256
243+"""
244+ expected_config = """\
245+# This is a demo boot config
246+[pi4]
247+max_framebuffers=2
248+[all]
249+arm_64bit=1
250+kernel=vmlinuz
251+initramfs initrd.img followkernel
252+# changed by do-release-upgrade (LP: #1923673)
253+#dtoverlay=vc4-fkms-v3d,cma-256
254+dtoverlay=vc4-kms-v3d,cma-256
255+# disabled by do-release-upgrade (LP: #1923673)
256+#start_x=1
257+# disabled by do-release-upgrade (LP: #1923673)
258+#gpu_mem=256
259+"""
260 with open(os.path.join(boot_dir, 'config.txt'), 'w') as f:
261 f.write(demo_config)
262
263@@ -519,6 +522,205 @@ class TestQuirks(unittest.TestCase):
264 with open(os.path.join(boot_dir, 'config.txt')) as f:
265 self.assertTrue(f.read() == expected_config)
266
267+ def test_remove_uboot_no_config(self):
268+ with tempfile.TemporaryDirectory() as boot_dir:
269+ mock_controller = mock.Mock()
270+ q = DistUpgradeQuirks(mock_controller, mock.Mock())
271+ q._remove_uboot_on_rpi(boot_dir)
272+
273+ self.assertFalse(os.path.exists(os.path.join(
274+ boot_dir, 'config.txt.distUpgrade')))
275+
276+ def test_remove_uboot_no_changes(self):
277+ with tempfile.TemporaryDirectory() as boot_dir:
278+ native_config = """\
279+# This is a demo boot config with a comment at the start that should not
280+# be removed
281+
282+[pi4]
283+max_framebuffers=2
284+
285+[all]
286+arm_64bit=1
287+kernel=vmlinuz
288+initramfs initrd.img followkernel
289+
290+# This is a user-added include that should not be merged
291+include custom.txt
292+"""
293+ custom_config = """\
294+# This is the custom included configuration file
295+
296+hdmi_group=1
297+hdmi_mode=4
298+"""
299+ with open(os.path.join(boot_dir, 'config.txt'), 'w') as f:
300+ f.write(native_config)
301+ with open(os.path.join(boot_dir, 'custom.txt'), 'w') as f:
302+ f.write(custom_config)
303+
304+ mock_controller = mock.Mock()
305+ q = DistUpgradeQuirks(mock_controller, mock.Mock())
306+ q._remove_uboot_on_rpi(boot_dir)
307+
308+ self.assertFalse(os.path.exists(os.path.join(
309+ boot_dir, 'config.txt.distUpgrade')))
310+ self.assertFalse(os.path.exists(os.path.join(
311+ boot_dir, 'custom.txt.distUpgrade')))
312+ with open(os.path.join(boot_dir, 'config.txt')) as f:
313+ self.assertTrue(f.read() == native_config)
314+ with open(os.path.join(boot_dir, 'custom.txt')) as f:
315+ self.assertTrue(f.read() == custom_config)
316+
317+ def test_remove_uboot_with_changes(self):
318+ with tempfile.TemporaryDirectory() as boot_dir:
319+ config_txt = """\
320+# This is a warning that you should not edit this file. The upgrade should
321+# remove this comment
322+
323+[pi4]
324+# This is a comment that should be included
325+kernel=uboot_rpi_4.bin
326+max_framebuffers=2
327+
328+[pi2]
329+kernel=uboot_rpi_2.bin
330+
331+[pi3]
332+kernel=uboot_rpi_3.bin
333+
334+[all]
335+arm_64bit=1
336+device_tree_address=0x3000000
337+include syscfg.txt
338+include usercfg.txt
339+dtoverlay=vc4-fkms-v3d,cma-256
340+include custom.txt
341+"""
342+ usercfg_txt = """\
343+# Another chunk of warning text that should be skipped
344+"""
345+ syscfg_txt = """\
346+# Yet more warnings to exclude
347+dtparam=audio=on
348+dtparam=spi=on
349+enable_uart=1
350+"""
351+ custom_txt = """\
352+# This is a user-added file that should be left alone by the upgrade
353+[gpio4=1]
354+kernel=custom
355+"""
356+ expected_config_txt = """\
357+
358+[pi4]
359+# This is a comment that should be included
360+# commented by do-release-upgrade (LP: #1936401)
361+#kernel=uboot_rpi_4.bin
362+max_framebuffers=2
363+
364+[pi2]
365+# commented by do-release-upgrade (LP: #1936401)
366+#kernel=uboot_rpi_2.bin
367+
368+[pi3]
369+# commented by do-release-upgrade (LP: #1936401)
370+#kernel=uboot_rpi_3.bin
371+
372+[all]
373+# added by do-release-upgrade (LP: #1936401)
374+kernel=vmlinuz
375+initramfs initrd.img followkernel
376+arm_64bit=1
377+# commented by do-release-upgrade (LP: #1936401)
378+#device_tree_address=0x3000000
379+# merged from syscfg.txt by do-release-upgrade (LP: #1936401)
380+dtparam=audio=on
381+dtparam=spi=on
382+enable_uart=1
383+# merged from usercfg.txt by do-release-upgrade (LP: #1936401)
384+dtoverlay=vc4-fkms-v3d,cma-256
385+include custom.txt
386+"""
387+ with open(os.path.join(boot_dir, 'config.txt'), 'w') as f:
388+ f.write(config_txt)
389+ with open(os.path.join(boot_dir, 'syscfg.txt'), 'w') as f:
390+ f.write(syscfg_txt)
391+ with open(os.path.join(boot_dir, 'usercfg.txt'), 'w') as f:
392+ f.write(usercfg_txt)
393+ with open(os.path.join(boot_dir, 'custom.txt'), 'w') as f:
394+ f.write(custom_txt)
395+
396+ mock_controller = mock.Mock()
397+ q = DistUpgradeQuirks(mock_controller, mock.Mock())
398+ q._remove_uboot_on_rpi(boot_dir)
399+
400+ self.assertTrue(os.path.exists(os.path.join(
401+ boot_dir, 'config.txt.distUpgrade')))
402+ self.assertTrue(os.path.exists(os.path.join(
403+ boot_dir, 'syscfg.txt.distUpgrade')))
404+ self.assertTrue(os.path.exists(os.path.join(
405+ boot_dir, 'usercfg.txt.distUpgrade')))
406+ self.assertTrue(os.path.exists(os.path.join(
407+ boot_dir, 'config.txt')))
408+ self.assertTrue(os.path.exists(os.path.join(
409+ boot_dir, 'custom.txt')))
410+ self.assertFalse(os.path.exists(os.path.join(
411+ boot_dir, 'syscfg.txt')))
412+ self.assertFalse(os.path.exists(os.path.join(
413+ boot_dir, 'usercfg.txt')))
414+ self.assertFalse(os.path.exists(os.path.join(
415+ boot_dir, 'custom.txt.distUpgrade')))
416+ with open(os.path.join(boot_dir, 'config.txt')) as f:
417+ self.assertTrue(f.read() == expected_config_txt)
418+ with open(os.path.join(boot_dir, 'custom.txt')) as f:
419+ self.assertTrue(f.read() == custom_txt)
420+
421+ def test_remove_uboot_no_all_section(self):
422+ with tempfile.TemporaryDirectory() as boot_dir:
423+ config_txt = """\
424+arm_64bit=1
425+device_tree_address=0x3000000
426+
427+[pi4]
428+# This is a comment that should be included
429+kernel=uboot_rpi_4.bin
430+max_framebuffers=2
431+
432+[pi3]
433+kernel=uboot_rpi_3.bin
434+"""
435+ expected_config_txt = """\
436+arm_64bit=1
437+# commented by do-release-upgrade (LP: #1936401)
438+#device_tree_address=0x3000000
439+
440+[pi4]
441+# This is a comment that should be included
442+# commented by do-release-upgrade (LP: #1936401)
443+#kernel=uboot_rpi_4.bin
444+max_framebuffers=2
445+
446+[pi3]
447+# commented by do-release-upgrade (LP: #1936401)
448+#kernel=uboot_rpi_3.bin
449+# added by do-release-upgrade (LP: #1936401)
450+[all]
451+kernel=vmlinuz
452+initramfs initrd.img followkernel
453+"""
454+ with open(os.path.join(boot_dir, 'config.txt'), 'w') as f:
455+ f.write(config_txt)
456+
457+ mock_controller = mock.Mock()
458+ q = DistUpgradeQuirks(mock_controller, mock.Mock())
459+ q._remove_uboot_on_rpi(boot_dir)
460+
461+ self.assertTrue(os.path.exists(os.path.join(
462+ boot_dir, 'config.txt.distUpgrade')))
463+ with open(os.path.join(boot_dir, 'config.txt')) as f:
464+ self.assertTrue(f.read() == expected_config_txt)
465+
466
467 class TestSnapQuirks(unittest.TestCase):
468

Subscribers

People subscribed via source and target branches