]>
Commit | Line | Data |
---|---|---|
1 | # | |
2 | # Copyright 2019-2021 Canonical Ltd. | |
3 | # Authors: | |
4 | # - dann frazier <dann.frazier@canonical.com> | |
5 | # | |
6 | # This program is free software: you can redistribute it and/or modify it | |
7 | # under the terms of the GNU General Public License version 3, as published | |
8 | # by the Free Software Foundation. | |
9 | # | |
10 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
11 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, | |
12 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | # General Public License for more details. | |
14 | # | |
15 | # You should have received a copy of the GNU General Public License along with | |
16 | # this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | # | |
18 | ||
19 | import enum | |
20 | import os | |
21 | import shutil | |
22 | import tempfile | |
23 | ||
24 | ||
25 | class QemuEfiMachine(enum.Enum): | |
26 | OVMF_PC = enum.auto() | |
27 | OVMF_Q35 = enum.auto() | |
28 | OVMF32 = enum.auto() | |
29 | AAVMF = enum.auto() | |
30 | AAVMF32 = enum.auto() | |
31 | ||
32 | ||
33 | class QemuEfiVariant(enum.Enum): | |
34 | MS = enum.auto() | |
35 | SECBOOT = enum.auto() | |
36 | SNAKEOIL = enum.auto() | |
37 | ||
38 | ||
39 | class QemuEfiFlashSize(enum.Enum): | |
40 | DEFAULT = enum.auto | |
41 | SIZE_2MB = enum.auto() | |
42 | SIZE_4MB = enum.auto() | |
43 | ||
44 | ||
45 | class QemuCommand: | |
46 | Qemu_Common_Params = [ | |
47 | '-no-user-config', '-nodefaults', | |
48 | '-m', '256', | |
49 | '-smp', '1,sockets=1,cores=1,threads=1', | |
50 | '-display', 'none', | |
51 | '-serial', 'stdio', | |
52 | ] | |
53 | Ovmf_Common_Params = Qemu_Common_Params + [ | |
54 | '-chardev', 'pty,id=charserial1', | |
55 | '-device', 'isa-serial,chardev=charserial1,id=serial1', | |
56 | ] | |
57 | Aavmf_Common_Params = Qemu_Common_Params + [ | |
58 | '-machine', 'virt', '-device', 'virtio-serial-device', | |
59 | ] | |
60 | Machine_Base_Command = { | |
61 | QemuEfiMachine.AAVMF: [ | |
62 | 'qemu-system-aarch64', '-cpu', 'cortex-a57', | |
63 | ] + Aavmf_Common_Params, | |
64 | QemuEfiMachine.AAVMF32: [ | |
65 | 'qemu-system-aarch64', '-cpu', 'cortex-a15', | |
66 | ] + Aavmf_Common_Params, | |
67 | QemuEfiMachine.OVMF_PC: [ | |
68 | 'qemu-system-x86_64', '-machine', 'pc,accel=tcg', | |
69 | ] + Ovmf_Common_Params, | |
70 | QemuEfiMachine.OVMF_Q35: [ | |
71 | 'qemu-system-x86_64', '-machine', 'q35,accel=tcg', | |
72 | ] + Ovmf_Common_Params, | |
73 | QemuEfiMachine.OVMF32: [ | |
74 | 'qemu-system-i386', '-machine', 'q35,accel=tcg', | |
75 | ] + Ovmf_Common_Params, | |
76 | } | |
77 | ||
78 | def _get_default_flash_paths(self, machine, variant, flash_size): | |
79 | assert(machine in QemuEfiMachine) | |
80 | assert(variant is None or variant in QemuEfiVariant) | |
81 | assert(flash_size in QemuEfiFlashSize) | |
82 | ||
83 | code_ext = vars_ext = '' | |
84 | if variant == QemuEfiVariant.MS: | |
85 | code_ext = vars_ext = '.ms' | |
86 | elif variant == QemuEfiVariant.SECBOOT: | |
87 | code_ext = '.secboot' | |
88 | elif variant == QemuEfiVariant.SNAKEOIL: | |
89 | vars_ext = '.snakeoil' | |
90 | ||
91 | if machine == QemuEfiMachine.AAVMF: | |
92 | assert(flash_size == QemuEfiFlashSize.DEFAULT) | |
93 | return ( | |
94 | f'/usr/share/AAVMF/AAVMF_CODE{code_ext}.fd', | |
95 | f'/usr/share/AAVMF/AAVMF_VARS{code_ext}.fd', | |
96 | ) | |
97 | if machine == QemuEfiMachine.AAVMF32: | |
98 | assert(variant is None) | |
99 | assert(flash_size == QemuEfiFlashSize.DEFAULT) | |
100 | return ( | |
101 | '/usr/share/AAVMF/AAVMF32_CODE.fd', | |
102 | '/usr/share/AAVMF/AAVMF32_VARS.fd' | |
103 | ) | |
104 | if machine == QemuEfiMachine.OVMF32: | |
105 | assert(variant is None or variant in [QemuEfiVariant.SECBOOT]) | |
106 | assert( | |
107 | flash_size in [ | |
108 | QemuEfiFlashSize.DEFAULT, QemuEfiFlashSize.SIZE_4MB | |
109 | ] | |
110 | ) | |
111 | return ( | |
112 | '/usr/share/OVMF/OVMF32_CODE_4M.secboot.fd', | |
113 | '/usr/share/OVMF/OVMF32_VARS_4M.fd', | |
114 | ) | |
115 | # Remaining possibilities are OVMF variants | |
116 | if machine == QemuEfiMachine.OVMF_PC: | |
117 | assert(variant is None) | |
118 | if variant == QemuEfiVariant.SNAKEOIL: | |
119 | # We provide one size - you don't get to pick. | |
120 | assert(flash_size == QemuEfiFlashSize.DEFAULT) | |
121 | size_ext = '' if flash_size == QemuEfiFlashSize.SIZE_2MB else '_4M' | |
122 | return ( | |
123 | f'/usr/share/OVMF/OVMF_CODE{size_ext}{code_ext}.fd', | |
124 | f'/usr/share/OVMF/OVMF_VARS{size_ext}{vars_ext}.fd' | |
125 | ) | |
126 | ||
127 | def __init__( | |
128 | self, machine, variant=None, | |
129 | code_path=None, vars_template_path=None, | |
130 | flash_size=QemuEfiFlashSize.DEFAULT, | |
131 | ): | |
132 | assert( | |
133 | (code_path and vars_template_path) or | |
134 | (not code_path and not vars_template_path) | |
135 | ) | |
136 | ||
137 | if not code_path: | |
138 | (code_path, vars_template_path) = self._get_default_flash_paths( | |
139 | machine, variant, flash_size) | |
140 | ||
141 | self.pflash = self.PflashParams(code_path, vars_template_path) | |
142 | self.command = self.Machine_Base_Command[machine] + self.pflash.params | |
143 | if variant in [QemuEfiVariant.MS, QemuEfiVariant.SECBOOT] and \ | |
144 | flash_size == QemuEfiFlashSize.SIZE_2MB: | |
145 | # 2MB images have 64-bit PEI that does not support S3 w/ SMM | |
146 | self.command.extend(['-global', 'ICH9-LPC.disable_s3=1']) | |
147 | ||
148 | def add_disk(self, path): | |
149 | self.command = self.command + [ | |
150 | '-drive', 'file=%s,format=raw' % (path) | |
151 | ] | |
152 | ||
153 | def add_oem_string(self, type, string): | |
154 | string = string.replace(",", ",,") | |
155 | self.command = self.command + [ | |
156 | '-smbios', f'type={type},value={string}' | |
157 | ] | |
158 | ||
159 | class PflashParams: | |
160 | ''' | |
161 | Used to generate the appropriate -pflash arguments for QEMU. Mostly | |
162 | used as a fancy way to generate a per-instance vars file and have it | |
163 | be automatically cleaned up when the object is destroyed. | |
164 | ''' | |
165 | def __init__(self, code_path, vars_template_path): | |
166 | with tempfile.NamedTemporaryFile(delete=False) as varfile: | |
167 | self.varfile_path = varfile.name | |
168 | with open(vars_template_path, 'rb') as template: | |
169 | shutil.copyfileobj(template, varfile) | |
170 | self.params = [ | |
171 | '-drive', | |
172 | 'file=%s,if=pflash,format=raw,unit=0,readonly=on' % | |
173 | (code_path), | |
174 | '-drive', | |
175 | 'file=%s,if=pflash,format=raw,unit=1,readonly=off' % | |
176 | (varfile.name) | |
177 | ] | |
178 | ||
179 | def __del__(self): | |
180 | os.unlink(self.varfile_path) |