1515import utils
1616
1717BOOT_UTILS = Path (__file__ ).resolve ().parent
18- SUPPORTED_ARCHES = ['x86' , 'x86_64' ]
18+ SUPPORTED_ARCHES = ['arm64' , 'arm64be' , ' x86' , 'x86_64' ]
1919
2020
2121class 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+
255383class 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