1# SPDX-License-Identifier: LGPL-2.1-or-later
2
3conf.set10('ENABLE_EFI', get_option('efi'))
4conf.set10('HAVE_GNU_EFI', false)
5
6efi_config_h_dir = meson.current_build_dir()
7
8if not get_option('efi') or get_option('gnu-efi') == 'false'
9        if get_option('gnu-efi') == 'true'
10                error('gnu-efi support requested, but general efi support is disabled')
11        endif
12        subdir_done()
13endif
14
15efi_arch = host_machine.cpu_family()
16if efi_arch == 'x86' and '-m64' in get_option('efi-cflags')
17        efi_arch = 'x86_64'
18elif efi_arch == 'x86_64' and '-m32' in get_option('efi-cflags')
19        efi_arch = 'x86'
20endif
21efi_arch = {
22        # host_cc_arch: [efi_arch (see Table 3-2 in UEFI spec), gnu_efi_inc_arch]
23        'x86':     ['ia32', 'ia32'],
24        'x86_64':  ['x64', 'x86_64'],
25        'arm':     ['arm', 'arm'],
26        'aarch64': ['aa64', 'aarch64'],
27        'riscv64': ['riscv64', 'riscv64'],
28}.get(efi_arch, [])
29
30efi_incdir = get_option('efi-includedir')
31if efi_arch.length() > 0 and not cc.has_header(
32                '@0@/@1@/efibind.h'.format(efi_incdir, efi_arch[1]),
33                args: get_option('efi-cflags'))
34
35        efi_arch = []
36endif
37
38if efi_arch.length() == 0
39        if get_option('gnu-efi') == 'true'
40                error('gnu-efi support requested, but headers not found or efi arch is unknown')
41        endif
42        warning('gnu-efi headers not found or efi arch is unknown, disabling gnu-efi support')
43        subdir_done()
44endif
45
46if not cc.has_header_symbol('efi.h', 'EFI_IMAGE_MACHINE_X64',
47                args: ['-nostdlib', '-ffreestanding', '-fshort-wchar'] + get_option('efi-cflags'),
48                include_directories: include_directories(efi_incdir, efi_incdir / efi_arch[1]))
49
50        if get_option('gnu-efi') == 'true'
51                error('gnu-efi support requested, but found headers are too old (3.0.5+ required)')
52        endif
53        warning('gnu-efi headers are too old (3.0.5+ required), disabling gnu-efi support')
54        subdir_done()
55endif
56
57objcopy = run_command(cc.cmd_array(), '-print-prog-name=objcopy', check: true).stdout().strip()
58
59efi_ld = get_option('efi-ld')
60if efi_ld == 'auto'
61        efi_ld = cc.get_linker_id().split('.')[1]
62        if efi_ld not in ['bfd', 'gold']
63                warning('Not using @0@ as efi-ld, falling back to bfd'.format(efi_ld))
64                efi_ld = 'bfd'
65        endif
66endif
67
68efi_multilib = run_command(
69        cc.cmd_array(), '-print-multi-os-directory', get_option('efi-cflags'),
70        check: false
71).stdout().strip()
72efi_multilib = run_command(
73        'realpath', '-e', '/usr/lib' / efi_multilib,
74        check: false
75).stdout().strip()
76
77efi_libdir = ''
78foreach dir : [get_option('efi-libdir'),
79               '/usr/lib/gnuefi' / efi_arch[0],
80               efi_multilib]
81        if dir != '' and fs.is_dir(dir)
82                efi_libdir = dir
83                break
84        endif
85endforeach
86if efi_libdir == ''
87        if get_option('gnu-efi') == 'true'
88                error('gnu-efi support requested, but efi-libdir was not found')
89        endif
90        warning('efi-libdir was not found, disabling gnu-efi support')
91        subdir_done()
92endif
93
94efi_lds = ''
95foreach location : [ # New locations first introduced with gnu-efi 3.0.11
96                    [efi_libdir / 'efi.lds',
97                     efi_libdir / 'crt0.o'],
98                    # Older locations...
99                    [efi_libdir / 'gnuefi' / 'elf_@0@_efi.lds'.format(efi_arch[1]),
100                     efi_libdir / 'gnuefi' / 'crt0-efi-@0@.o'.format(efi_arch[1])],
101                    [efi_libdir / 'elf_@0@_efi.lds'.format(efi_arch[1]),
102                     efi_libdir / 'crt0-efi-@0@.o'.format(efi_arch[1])]]
103        if fs.is_file(location[0]) and fs.is_file(location[1])
104                efi_lds = location[0]
105                efi_crt0 = location[1]
106                break
107        endif
108endforeach
109if efi_lds == ''
110        if get_option('gnu-efi') == 'true'
111                error('gnu-efi support requested, but cannot find efi.lds')
112        endif
113        warning('efi.lds was not found, disabling gnu-efi support')
114        subdir_done()
115endif
116
117conf.set10('HAVE_GNU_EFI', true)
118conf.set_quoted('EFI_MACHINE_TYPE_NAME', efi_arch[0])
119
120efi_conf = configuration_data()
121efi_conf.set_quoted('EFI_MACHINE_TYPE_NAME', efi_arch[0])
122efi_conf.set10('ENABLE_TPM', get_option('tpm'))
123efi_conf.set10('EFI_TPM_PCR_COMPAT', get_option('efi-tpm-pcr-compat'))
124
125foreach ctype : ['color-normal', 'color-entry', 'color-highlight', 'color-edit']
126        c = get_option('efi-' + ctype).split(',')
127        efi_conf.set(ctype.underscorify().to_upper(), 'EFI_TEXT_ATTR(@0@, @1@)'.format(
128                'EFI_' + c[0].strip().underscorify().to_upper(),
129                'EFI_' + c[1].strip().underscorify().to_upper()))
130endforeach
131
132if meson.is_cross_build() and get_option('sbat-distro') == 'auto'
133        warning('Auto detection of SBAT information not supported when cross-building, disabling SBAT.')
134elif get_option('sbat-distro') != ''
135        efi_conf.set_quoted('SBAT_PROJECT', meson.project_name())
136        efi_conf.set_quoted('PROJECT_VERSION', meson.project_version())
137        efi_conf.set('PROJECT_URL', conf.get('PROJECT_URL'))
138        if get_option('sbat-distro-generation') < 1
139                error('SBAT Distro Generation must be a positive integer')
140        endif
141        efi_conf.set('SBAT_DISTRO_GENERATION', get_option('sbat-distro-generation'))
142        foreach sbatvar : [['sbat-distro', 'ID'],
143                           ['sbat-distro-summary', 'NAME'],
144                           ['sbat-distro-url', 'BUG_REPORT_URL']]
145                value = get_option(sbatvar[0])
146                if (value == '' or value == 'auto') and not meson.is_cross_build()
147                        cmd = 'if [ -e /etc/os-release ]; then . /etc/os-release; else . /usr/lib/os-release; fi; echo $@0@'.format(sbatvar[1])
148                        value = run_command(sh, '-c', cmd, check: true).stdout().strip()
149                endif
150                if value == ''
151                        error('Required @0@ option not set and autodetection failed'.format(sbatvar[0]))
152                endif
153                efi_conf.set_quoted(sbatvar[0].underscorify().to_upper(), value)
154        endforeach
155
156        pkgname = get_option('sbat-distro-pkgname')
157        if pkgname == ''
158                pkgname = meson.project_name()
159        endif
160        efi_conf.set_quoted('SBAT_DISTRO_PKGNAME', pkgname)
161
162        pkgver = get_option('sbat-distro-version')
163        if pkgver == ''
164                efi_conf.set('SBAT_DISTRO_VERSION', 'GIT_VERSION')
165                # This is determined during build, not configuration, so we can't display it yet.
166                sbat_distro_version_display = '(git version)'
167        else
168                efi_conf.set_quoted('SBAT_DISTRO_VERSION', pkgver)
169                sbat_distro_version_display = pkgver
170        endif
171endif
172
173efi_config_h = configure_file(
174        output : 'efi_config.h',
175        configuration : efi_conf)
176
177efi_cflags = cc.get_supported_arguments(
178        basic_disabled_warnings +
179        possible_common_cc_flags + [
180                '-fno-stack-protector',
181                '-fno-strict-aliasing',
182                '-fpic',
183                '-fwide-exec-charset=UCS2',
184                '-Wall',
185                '-Wextra',
186                '-Wsign-compare',
187        ]
188) + [
189        '-nostdlib',
190        '-std=gnu11',
191        '-ffreestanding',
192        '-fshort-wchar',
193        '-fvisibility=hidden',
194        '-isystem', efi_incdir,
195        '-isystem', efi_incdir / efi_arch[1],
196        '-I', fundamental_path,
197        '-DSD_BOOT',
198        '-DGNU_EFI_USE_MS_ABI',
199        '-include', efi_config_h,
200        '-include', version_h,
201]
202
203efi_cflags += cc.get_supported_arguments({
204        'ia32':   ['-mno-sse', '-mno-mmx'],
205        'x86_64': ['-mno-red-zone', '-mno-sse', '-mno-mmx'],
206        'arm':    ['-mgeneral-regs-only', '-mfpu=none'],
207}.get(efi_arch[1], []))
208
209# We are putting the efi_cc command line together ourselves, so make sure to pull any
210# relevant compiler flags from meson/CFLAGS as povided by the user or distro.
211
212if get_option('werror')
213        efi_cflags += ['-Werror']
214endif
215if get_option('debug') and get_option('mode') == 'developer'
216        efi_cflags += ['-ggdb', '-DEFI_DEBUG']
217endif
218if get_option('optimization') != '0'
219        efi_cflags += ['-O' + get_option('optimization')]
220endif
221if get_option('b_ndebug') == 'true' or (
222   get_option('b_ndebug') == 'if-release' and get_option('buildtype') in ['plain', 'release'])
223        efi_cflags += ['-DNDEBUG']
224endif
225if get_option('b_lto')
226        efi_cflags += ['-flto']
227endif
228
229foreach arg : get_option('c_args')
230        if arg in [
231                '-DNDEBUG',
232                '-fno-lto',
233                '-O1', '-O2', '-O3', '-Og', '-Os',
234                '-Werror',
235           ] or arg.split('=')[0] in [
236                '-ffile-prefix-map',
237                '-flto',
238           ] or (get_option('mode') == 'developer' and arg in [
239                '-DEFI_DEBUG',
240                '-g', '-ggdb',
241           ])
242
243                message('Using "@0@" from c_args for EFI compiler'.format(arg))
244                efi_cflags += arg
245        endif
246endforeach
247
248efi_cflags += get_option('efi-cflags')
249
250efi_ldflags = [
251        '-fuse-ld=' + efi_ld,
252        '-L', efi_libdir,
253        '-nostdlib',
254        '-T', efi_lds,
255        '-Wl,--build-id=sha1',
256        '-Wl,--fatal-warnings',
257        '-Wl,--no-undefined',
258        '-Wl,--warn-common',
259        '-Wl,-Bsymbolic',
260        '-z', 'nocombreloc',
261        efi_crt0,
262]
263if efi_arch[1] in ['aarch64', 'arm', 'riscv64']
264        efi_ldflags += ['-shared']
265        # Aarch64, ARM32 and 64bit RISC-V don't have an EFI capable objcopy.
266        # Use 'binary' instead, and add required symbols manually.
267        efi_ldflags += ['-Wl,--defsym=EFI_SUBSYSTEM=0xa']
268        efi_format = ['-O', 'binary']
269else
270        efi_ldflags += ['-pie']
271        if efi_ld == 'bfd'
272                efi_ldflags += '-Wl,--no-dynamic-linker'
273        endif
274        efi_format = ['--target=efi-app-@0@'.format(efi_arch[1])]
275endif
276
277if efi_arch[1] == 'arm'
278        # On arm, the compiler (correctly) warns about wchar_t size mismatch. This
279        # is because libgcc is not compiled with -fshort-wchar, but it does not
280        # have any occurrences of wchar_t in its sources or the documentation, so
281        # it is safe to assume that we can ignore this warning.
282        efi_ldflags += ['-Wl,--no-wchar-size-warning']
283endif
284
285if run_command('grep', '-q', '__CTOR_LIST__', efi_lds, check: false).returncode() == 0
286        # fedora has a patched gnu-efi that adds support for ELF constructors.
287        # If ld is called by gcc something about these symbols breaks, resulting
288        # in sd-boot freezing when gnu-efi runs the constructors. Force defining
289        # them seems to work around this.
290        efi_ldflags += [
291                '-Wl,--defsym=_init_array=0',
292                '-Wl,--defsym=_init_array_end=0',
293                '-Wl,--defsym=_fini_array=0',
294                '-Wl,--defsym=_fini_array_end=0',
295                '-Wl,--defsym=__CTOR_LIST__=0',
296                '-Wl,--defsym=__CTOR_END__=0',
297                '-Wl,--defsym=__DTOR_LIST__=0',
298                '-Wl,--defsym=__DTOR_END__=0',
299        ]
300endif
301
302if cc.get_id() == 'clang' and cc.version().split('.')[0].to_int() <= 10
303        # clang <= 10 doesn't pass -T to the linker and then even complains about it being unused
304        efi_ldflags += ['-Wl,-T,' + efi_lds, '-Wno-unused-command-line-argument']
305endif
306
307summary({
308        'EFI machine type' :                efi_arch[0],
309        'EFI LD' :                          efi_ld,
310        'EFI lds' :                         efi_lds,
311        'EFI crt0' :                        efi_crt0,
312        'EFI include directory' :           efi_incdir},
313        section : 'Extensible Firmware Interface')
314
315if efi_conf.get('SBAT_DISTRO', '') != ''
316        summary({
317                'SBAT distro':              efi_conf.get('SBAT_DISTRO'),
318                'SBAT distro generation':   efi_conf.get('SBAT_DISTRO_GENERATION'),
319                'SBAT distro version':      sbat_distro_version_display,
320                'SBAT distro summary':      efi_conf.get('SBAT_DISTRO_SUMMARY'),
321                'SBAT distro URL':          efi_conf.get('SBAT_DISTRO_URL')},
322                section : 'Extensible Firmware Interface')
323endif
324
325############################################################
326
327efi_headers = files(
328        'bcd.h',
329        'console.h',
330        'cpio.h',
331        'devicetree.h',
332        'disk.h',
333        'drivers.h',
334        'graphics.h',
335        'initrd.h',
336        'linux.h',
337        'measure.h',
338        'missing_efi.h',
339        'pe.h',
340        'random-seed.h',
341        'secure-boot.h',
342        'shim.h',
343        'splash.h',
344        'ticks.h',
345        'util.h',
346        'xbootldr.h',
347)
348
349common_sources = files(
350        'assert.c',
351        'devicetree.c',
352        'disk.c',
353        'graphics.c',
354        'measure.c',
355        'pe.c',
356        'secure-boot.c',
357        'ticks.c',
358        'util.c',
359)
360
361systemd_boot_sources = files(
362        'boot.c',
363        'console.c',
364        'drivers.c',
365        'random-seed.c',
366        'shim.c',
367        'xbootldr.c',
368)
369
370stub_sources = files(
371        'cpio.c',
372        'initrd.c',
373        'splash.c',
374        'stub.c',
375)
376
377if efi_arch[1] in ['ia32', 'x86_64']
378        stub_sources += files('linux_x86.c')
379else
380        stub_sources += files('linux.c')
381endif
382
383# BCD parser only makes sense on arches that Windows supports.
384if efi_arch[1] in ['ia32', 'x86_64', 'arm', 'aarch64']
385        systemd_boot_sources += files('bcd.c')
386        tests += [
387                [files('test-bcd.c'),
388                 [],
389                 [libzstd],
390                 [],
391                 'HAVE_ZSTD'],
392        ]
393        fuzzers += [
394                [files('fuzz-bcd.c')],
395        ]
396endif
397
398systemd_boot_objects = []
399stub_objects = []
400foreach file : fundamental_source_paths + common_sources + systemd_boot_sources + stub_sources
401        # FIXME: replace ''.format(file) with fs.name(file) when meson_version requirement is >= 0.59.0
402        o_file = custom_target('@0@.o'.format(file).split('/')[-1],
403                               input : file,
404                               output : '@0@.o'.format(file).split('/')[-1],
405                               command : [cc.cmd_array(), '-c', '@INPUT@', '-o', '@OUTPUT@', efi_cflags],
406                               depend_files : efi_headers + fundamental_headers)
407        if (fundamental_source_paths + common_sources + systemd_boot_sources).contains(file)
408                systemd_boot_objects += o_file
409        endif
410        if (fundamental_source_paths + common_sources + stub_sources).contains(file)
411                stub_objects += o_file
412        endif
413endforeach
414
415foreach tuple : [['systemd-boot@0@.@1@', systemd_boot_objects, false, 'systemd-boot'],
416                 ['linux@0@.@1@.stub', stub_objects, true, 'systemd-stub']]
417        elf = custom_target(
418                tuple[0].format(efi_arch[0], 'elf'),
419                input : tuple[1],
420                output : tuple[0].format(efi_arch[0], 'elf'),
421                command : [cc.cmd_array(),
422                           '-o', '@OUTPUT@',
423                           efi_cflags,
424                           efi_ldflags,
425                           '@INPUT@',
426                           '-lefi',
427                           '-lgnuefi',
428                           '-lgcc'],
429                install : tuple[2],
430                install_tag: tuple[3],
431                install_dir : bootlibdir)
432
433        efi = custom_target(
434                tuple[0].format(efi_arch[0], 'efi'),
435                input : elf,
436                output : tuple[0].format(efi_arch[0], 'efi'),
437                command : [objcopy,
438                           '-j', '.bss*',
439                           '-j', '.data',
440                           '-j', '.dynamic',
441                           '-j', '.dynsym',
442                           '-j', '.osrel',
443                           '-j', '.rel*',
444                           '-j', '.sbat',
445                           '-j', '.sdata',
446                           '-j', '.sdmagic',
447                           '-j', '.text',
448                           '--section-alignment=512',
449                           efi_format,
450                           '@INPUT@', '@OUTPUT@'],
451                install : true,
452                install_tag: tuple[3],
453                install_dir : bootlibdir)
454
455        alias_target(tuple[3], efi)
456endforeach
457