Skip to content

Commit d5353c9

Browse files
committed
boot-qemu.py: Add support for arm64
Signed-off-by: Nathan Chancellor <nathan@kernel.org>
1 parent 22a0fa1 commit d5353c9

1 file changed

Lines changed: 160 additions & 30 deletions

File tree

boot-qemu.py

Lines changed: 160 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import utils
1616

1717
BOOT_UTILS = Path(__file__).resolve().parent
18-
SUPPORTED_ARCHES = ['x86', 'x86_64']
18+
SUPPORTED_ARCHES = ['arm64', 'arm64be', 'x86', 'x86_64']
1919

2020

2121
class QEMURunner:
@@ -83,6 +83,31 @@ def _get_default_smp_value(self):
8383
usable_cpus = os.cpu_count()
8484
return min(usable_cpus, config_nr_cpus)
8585

86+
def _get_kernel_ver_tuple(self, decomp_prog):
87+
if not self.kernel:
88+
raise RuntimeError('No kernel set?')
89+
90+
utils.check_cmd(decomp_prog)
91+
if decomp_prog in ('gzip', ):
92+
decomp_cmd = [decomp_prog, '-c', '-d', self.kernel]
93+
decomp = subprocess.run(decomp_cmd, capture_output=True, check=True)
94+
95+
utils.check_cmd('strings')
96+
strings = subprocess.run('strings',
97+
capture_output=True,
98+
check=True,
99+
input=decomp.stdout)
100+
strings_stdout = strings.stdout.decode(encoding='utf-8',
101+
errors='ignore')
102+
103+
if not (match := re.search(r'^Linux version (\d+\.\d+\.\d+)',
104+
strings_stdout,
105+
flags=re.M)):
106+
raise RuntimeError(
107+
f"Could not find Linux version in {self.kernel}?")
108+
109+
return tuple(int(x) for x in match.groups()[0].split('.'))
110+
86111
def _get_qemu_ver_string(self):
87112
if not self._qemu_path:
88113
raise RuntimeError('No path to QEMU set?')
@@ -92,6 +117,13 @@ def _get_qemu_ver_string(self):
92117
text=True)
93118
return qemu_ver.stdout.splitlines()[0]
94119

120+
def _get_qemu_ver_tuple(self):
121+
qemu_ver_string = self._get_qemu_ver_string()
122+
if not (match := re.search(r'version (\d+\.\d+.\d+)',
123+
qemu_ver_string)):
124+
raise RuntimeError('Could not find QEMU version?')
125+
return tuple(int(x) for x in match.groups()[0].split('.'))
126+
95127
def _have_dev_kvm_access(self):
96128
return os.access('/dev/kvm', os.R_OK | os.W_OK)
97129

@@ -167,8 +199,38 @@ def _run_gdb(self):
167199
if answer.lower() == 'n':
168200
break
169201

170-
def run(self):
171-
# Make sure QEMU binary is configured and available
202+
def _set_kernel_vars(self):
203+
if self.kernel:
204+
if not self.kernel_dir:
205+
self.kernel_dir = self.kernel.parent
206+
# Nothing else to do, kernel image and build folder located and set
207+
return
208+
209+
if not self.kernel_dir:
210+
raise RuntimeError(
211+
'No kernel image or kernel build folder specified?')
212+
if not self._default_kernel_path:
213+
raise RuntimeError('No default kernel path specified?')
214+
215+
possible_kernel_locations = {
216+
Path(self.kernel_dir,
217+
self._default_kernel_path), # default (kbuild)
218+
Path(self.kernel_dir, self._default_kernel_path.name), # tuxmake
219+
}
220+
for loc in possible_kernel_locations:
221+
if loc.exists():
222+
self.kernel = loc
223+
break
224+
if not self.kernel:
225+
possible_locations = "', '".join(
226+
str(path) for path in possible_kernel_locations)
227+
raise FileNotFoundError(
228+
f"{self._default_kernel_path.name} could not be found at possible locations ('{possible_locations}')",
229+
)
230+
231+
def _set_qemu_path(self):
232+
if self._qemu_path:
233+
return # already found and set
172234
if not self._qemu_arch:
173235
raise RuntimeError('No QEMU architecture set?')
174236
qemu_bin = f"qemu-system-{self._qemu_arch}"
@@ -177,33 +239,12 @@ def run(self):
177239
f'{qemu_bin} could not be found on your system?')
178240
self._qemu_path = Path(qemu_path)
179241

180-
# Locate kernel if it was not specified
181-
if self.kernel:
182-
if not self.kernel_dir:
183-
self.kernel_dir = self.kernel.parent
184-
else:
185-
if not self.kernel_dir:
186-
raise RuntimeError(
187-
'No kernel image or kernel build folder specified?')
188-
if not self._default_kernel_path:
189-
raise RuntimeError('No default kernel path specified?')
190-
191-
possible_kernel_locations = {
192-
Path(self.kernel_dir,
193-
self._default_kernel_path), # default (kbuild)
194-
Path(self.kernel_dir,
195-
self._default_kernel_path.name), # tuxmake
196-
}
197-
for loc in possible_kernel_locations:
198-
if loc.exists():
199-
self.kernel = loc
200-
break
201-
if not self.kernel:
202-
possible_locations = "', '".join(
203-
str(path) for path in possible_kernel_locations)
204-
raise FileNotFoundError(
205-
f"{self._default_kernel_path.name} could not be found at possible locations ('{possible_locations}')",
206-
)
242+
def run(self):
243+
# Make sure QEMU binary is configured and available
244+
self._set_qemu_path()
245+
246+
# Locate kernel (may be done earlier in subclasses)
247+
self._set_kernel_vars()
207248

208249
# EFI:
209250
if self.efi:
@@ -252,6 +293,93 @@ def supports_efi(self):
252293
return False
253294

254295

296+
class ARM64QEMURunner(QEMURunner):
297+
298+
def __init__(self):
299+
super().__init__()
300+
301+
self.cmdline += ['console=ttyAMA0', 'earlycon']
302+
303+
self._default_kernel_path = Path('arch/arm64/boot/Image.gz')
304+
self._initrd_arch = 'arm64'
305+
self._qemu_arch = 'aarch64'
306+
307+
def _can_use_kvm(self):
308+
return platform.machine() == 'aarch64' and self._have_dev_kvm_access()
309+
310+
def run(self):
311+
machine = ['virt', 'gic-version=max']
312+
313+
if not self.use_kvm:
314+
cpu = ['max']
315+
316+
self._set_qemu_path()
317+
if (qemu_ver := self._get_qemu_ver_tuple()) >= (6, 2, 50):
318+
self._set_kernel_vars()
319+
kernel_ver = self._get_kernel_ver_tuple('gzip')
320+
321+
# https://gitlab.com/qemu-project/qemu/-/issues/964
322+
if kernel_ver < (4, 16, 0):
323+
cpu = ['cortex-a72']
324+
# https://gitlab.com/qemu-project/qemu/-/commit/69b2265d5fe8e0f401d75e175e0a243a7d505e53
325+
elif kernel_ver < (5, 12, 0):
326+
cpu.append('lpa2=off')
327+
328+
# https://lore.kernel.org/YlgVa+AP0g4IYvzN@lakrids/
329+
if 'max' in cpu and qemu_ver >= (6, 0, 0):
330+
cpu.append('pauth-impdef=true')
331+
332+
self._qemu_args += ['-cpu', ','.join(cpu)]
333+
334+
# Boot with VHE emulation, which allows the kernel to run at EL2.
335+
# KVM does not emulate VHE, so this cannot be unconditional.
336+
machine.append('virtualization=true')
337+
338+
self._qemu_args += ['-machine', ','.join(machine)]
339+
340+
if self.efi:
341+
# Sizing the images to 64M is recommended by "Prepare the firmware" section at
342+
# https://mirrors.edge.kernel.org/pub/linux/kernel/people/will/docs/qemu/qemu-arm64-howto.html
343+
efi_img_size = 64 * 1024 * 1024 # 64M
344+
345+
usr_share = Path('/usr/share')
346+
347+
aavmf_locations = [
348+
Path('edk2/aarch64/QEMU_EFI.silent.fd'), # Fedora
349+
Path('edk2/aarch64/QEMU_EFI.fd'), # Arch Linux (current)
350+
Path('edk2-armvirt/aarch64/QEMU_EFI.fd'), # Arch Linux (old)
351+
Path('qemu-efi-aarch64/QEMU_EFI.fd'), # Debian and Ubuntu
352+
]
353+
aavmf = utils.find_first_file(usr_share, aavmf_locations)
354+
355+
self._efi_img = Path(BOOT_UTILS, 'images', self._initrd_arch,
356+
'efi.img')
357+
shutil.copyfile(aavmf, self._efi_img)
358+
with self._efi_img.open(mode='r+b') as file:
359+
file.truncate(efi_img_size)
360+
361+
self._efi_vars = self._efi_img.with_stem('efivars')
362+
self._efi_vars.unlink(missing_ok=True)
363+
with self._efi_vars.open(mode='xb') as file:
364+
file.truncate(efi_img_size)
365+
366+
super().run()
367+
368+
def supports_efi(self):
369+
return True
370+
371+
372+
class ARM64BEQEMURunner(ARM64QEMURunner):
373+
374+
def __init__(self):
375+
super().__init__()
376+
377+
self._initrd_arch = 'arm64be'
378+
379+
def supports_efi(self):
380+
return False
381+
382+
255383
class X86QEMURunner(QEMURunner):
256384

257385
def __init__(self):
@@ -370,6 +498,8 @@ def parse_arguments():
370498
args = parse_arguments()
371499

372500
arch_to_runner = {
501+
'arm64': ARM64QEMURunner,
502+
'arm64be': ARM64BEQEMURunner,
373503
'x86': X86QEMURunner,
374504
'x86_64': X8664QEMURunner,
375505
}

0 commit comments

Comments
 (0)