Skip to content

[libc][uefi] add crt1 #132150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
May 9, 2025
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion libc/include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,6 @@ add_header_macro(
add_header_macro(
uefi
../libc/include/Uefi.yaml
Uefi.h.def
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you're removing the usage of Uefi.h.def you should also remove the file.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember this change... Not sure why the line was removed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh now I remember, removed the file now.

Uefi.h
DEPENDS
.llvm_libc_common_h
Expand Down
4 changes: 2 additions & 2 deletions libc/include/Uefi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ types:
enums: []
functions: []
objects:
- object_name: efi_system_table
- object_name: __llvm_libc_efi_system_table
object_type: EFI_SYSTEM_TABLE *
- object_name: efi_image_handle
- object_name: __llvm_libc_efi_image_handle
object_type: EFI_HANDLE
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be adding LLVM libc internal symbols to standard headers like Uefi.h, rather these symbols should be declared in an internal header.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this renaming could be done in a separate PR since it's orthogonal to introducing crt1.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be adding LLVM libc internal symbols to standard headers like Uefi.h, rather these symbols should be declared in an internal header.

It's required to expose those symbols so programs can access UEFI specific things that are out of the scope of the libc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the intended usage for this? Is there a design doc I could read? That would make figuring out if this is the right approach a lot easier.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's intended usage is any UEFI thing that isn't covered by a libc (see UEFI spec for exact details). This would cover things like interacting with the PCI bus or USB interfaces to graphics and networking. I don't have a design doc nor am I aware of one. You need to be able to access the system table to get access to the UEFI environment itself.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there isn't a design doc, you should make one. It may take more time now, but writing out the plan for your interface will save time in the long run.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright and it'll cover #132150 (review) as well? I've never written a design doc before.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, one design doc would cover that as well. Don't stress too much about the initial design doc, part of the reason to create it is to get feedback and improve it.

The main thing to do is explain

  1. What is your goal (e.g. provide a UEFI API that can do X/Y/Z)
  2. How do you propose reaching that goal (e.g. Provide A/B/C libc functions)
  3. Why do you think this design is a good solution (e.g. What properties does it have that are useful, what alternatives did you consider)

Here's an example of a design doc I wrote last year: https://discourse.llvm.org/t/rfc-project-hand-in-hand-llvm-libc-libc-code-sharing/77701
Feel free to copy the formatting, though you can definitely skip the "work estimates" section. That came from a template I was using and didn't end up being relevant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UEFI spec doesn't specify how to retrieve EFI_SYSTEM_TABLE and EFI_HANDLE (other than the handoff protocol), all UEFI entry points take it as an explicit argument. These symbols are a non-standard extension to spec and as such we need to be extra careful. For example, I don't think we should place these symbols in Uefi.h since that's less portable than having a custom header (which also let's you use __has_include). There are other questions, like for example whether these should be symbols or functions (the latter having an advantage of being interposable). All of these aspects should ideally be covered in the RFC.

2 changes: 1 addition & 1 deletion libc/include/llvm-libc-types/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ add_header(EFI_HANDLE HDR EFI_HANDLE.h)
add_header(EFI_TIME HDR EFI_TIME.h DEPENDS libc.include.llvm-libc-macros.stdint_macros)
add_header(EFI_TIMER_DELAY HDR EFI_TIMER_DELAY.h)
add_header(EFI_TPL HDR EFI_TPL.h DEPENDS .size_t)
add_header(EFI_STATUS HDR EFI_STATUS.h DEPENDS .size_t)
add_header(EFI_STATUS HDR EFI_STATUS.h DEPENDS libc.include.llvm-libc-macros.stdint_macros)

add_header(EFI_OPEN_PROTOCOL_INFORMATION_ENTRY
HDR
Expand Down
48 changes: 46 additions & 2 deletions libc/include/llvm-libc-types/EFI_STATUS.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,52 @@
#ifndef LLVM_LIBC_TYPES_EFI_STATUS_H
#define LLVM_LIBC_TYPES_EFI_STATUS_H

#include "size_t.h"
#include "../llvm-libc-macros/stdint-macros.h"

typedef size_t EFI_STATUS;
typedef uintptr_t EFI_STATUS;

#define EFI_SUCCESS 0

#define EFI_LOAD_ERROR 1
#define EFI_INVALID_PARAMETER 2
#define EFI_UNSUPPORTED 3
#define EFI_BAD_BUFFER_SIZE 4
#define EFI_BUFFER_TOO_SMALL 5
#define EFI_NOT_READY 6
#define EFI_DEVICE_ERROR 7
#define EFI_WRITE_PROTECTED 8
#define EFI_OUT_OF_RESOURCES 9
#define EFI_VOLUME_CORRUPTED 10
#define EFI_VOLUME_FULL 11
#define EFI_NO_MEDIA 12
#define EFI_MEDIA_CHANGED 13
#define EFI_NOT_FOUND 14
#define EFI_ACCESS_DENIED 15
#define EFI_NO_RESPONSE 16
#define EFI_NO_MAPPING 17
#define EFI_TIMEOUT 18
#define EFI_NOT_STARTED 19
#define EFI_ALREADY_STARTED 20
#define EFI_ABORTED 21
#define EFI_ICMP_ERROR 22
#define EFI_TFTP_ERROR 23
#define EFI_PROTOCOL_ERROR 24
#define EFI_INCOMPATIBLE_VERSION 25
#define EFI_SECURITY_VIOLATION 26
#define EFI_CRC_ERROR 27
#define EFI_END_OF_MEDIA 28
#define EFI_END_OF_FILE 31
#define EFI_INVALID_LANGUAGE 32
#define EFI_COMPROMISED_DATA 33
#define EFI_IP_ADDRESS_CONFLICT 34
#define EFI_HTTP_ERROR 35

#define EFI_WARN_UNKNOWN_GLYPH 1
#define EFI_WARN_DELETE_FAILURE 2
#define EFI_WARN_WRITE_FAILURE 3
#define EFI_WARN_BUFFER_TOO_SMALL 4
#define EFI_WARN_STALE_DATA 5
#define EFI_WARN_FILE_SYSTEM 6
#define EFI_WARN_RESET_REQUIRED 7

#endif // LLVM_LIBC_TYPES_EFI_STATUS_H
1 change: 1 addition & 0 deletions libc/src/__support/OSUtil/uefi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ add_object_library(
HDRS
io.h
DEPENDS
libc.include.llvm-libc-types.EFI_SYSTEM_TABLE
libc.src.__support.common
libc.src.__support.CPP.string_view
)
96 changes: 96 additions & 0 deletions libc/src/__support/OSUtil/uefi/error.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//===----------- UEFI implementation of error utils --------------*- C++-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_UEFI_ERROR_H
#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_UEFI_ERROR_H

#include "include/llvm-libc-types/EFI_STATUS.h"
#include "src/__support/macros/config.h"
#include <errno.h>
#include <limits.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to avoid including public libc headers directly. Also the internal limits is templated so you can just use cpp::numeric_limits<EFI_STATUS>::max() to get the maximum.

Suggested change
#include <errno.h>
#include <limits.h>
#include "hdr/errno_macros.h"
#include "src/__support/CPP/limits.h"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using cpp::numeric_limits causes me to run into this issue:

error: constexpr variable 'UEFI_STATUS_ERRNO_MAP' must be initialized by a constant expression

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced constexpr with const and got error: shift count >= width of type:

#define EFI_ERROR_MAX_BIT (1 << (cpp::numeric_limits<EFI_STATUS>::max() - 1))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess EFI_ERROR_MAX_BIT as cpp::numeric_limits<EFI_STATUS>::max() is what I should be doing

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, this has been done.


namespace LIBC_NAMESPACE_DECL {

#define EFI_ERROR_MAX_BIT (1 << (sizeof(EFI_STATUS) * sizeof(char) - 1))
#define EFI_ENCODE_ERROR(value) \
(EFI_ERROR_MAX_BIT | (EFI_ERROR_MAX_BIT >> 2) | (value))
#define EFI_ENCODE_WARNING(value) ((EFI_ERROR_MAX_BIT >> 2) | (value))

static constexpr struct errno_efi_status_entry {
EFI_STATUS status;
int errno_value;
} UEFI_STATUS_ERRNO_MAP[] = {
{EFI_SUCCESS, 0},
{EFI_ENCODE_ERROR(EFI_LOAD_ERROR), EINVAL},
{EFI_ENCODE_ERROR(EFI_INVALID_PARAMETER), EINVAL},
{EFI_ENCODE_ERROR(EFI_BAD_BUFFER_SIZE), EINVAL},
{EFI_ENCODE_ERROR(EFI_NOT_READY), EBUSY},
{EFI_ENCODE_ERROR(EFI_DEVICE_ERROR), EIO},
{EFI_ENCODE_ERROR(EFI_WRITE_PROTECTED), EPERM},
{EFI_ENCODE_ERROR(EFI_OUT_OF_RESOURCES), ENOMEM},
{EFI_ENCODE_ERROR(EFI_VOLUME_CORRUPTED), EROFS},
{EFI_ENCODE_ERROR(EFI_VOLUME_FULL), ENOSPC},
{EFI_ENCODE_ERROR(EFI_NO_MEDIA), ENODEV},
{EFI_ENCODE_ERROR(EFI_MEDIA_CHANGED), ENXIO},
{EFI_ENCODE_ERROR(EFI_NOT_FOUND), ENOENT},
{EFI_ENCODE_ERROR(EFI_ACCESS_DENIED), EACCES},
{EFI_ENCODE_ERROR(EFI_NO_RESPONSE), EBUSY},
{EFI_ENCODE_ERROR(EFI_NO_MAPPING), ENODEV},
{EFI_ENCODE_ERROR(EFI_TIMEOUT), EBUSY},
{EFI_ENCODE_ERROR(EFI_NOT_STARTED), EAGAIN},
{EFI_ENCODE_ERROR(EFI_ALREADY_STARTED), EINVAL},
{EFI_ENCODE_ERROR(EFI_ABORTED), EFAULT},
{EFI_ENCODE_ERROR(EFI_ICMP_ERROR), EIO},
{EFI_ENCODE_ERROR(EFI_TFTP_ERROR), EIO},
{EFI_ENCODE_ERROR(EFI_PROTOCOL_ERROR), EINVAL},
{EFI_ENCODE_ERROR(EFI_INCOMPATIBLE_VERSION), EINVAL},
{EFI_ENCODE_ERROR(EFI_SECURITY_VIOLATION), EPERM},
{EFI_ENCODE_ERROR(EFI_CRC_ERROR), EINVAL},
{EFI_ENCODE_ERROR(EFI_END_OF_MEDIA), EPIPE},
{EFI_ENCODE_ERROR(EFI_END_OF_FILE), EPIPE},
{EFI_ENCODE_ERROR(EFI_INVALID_LANGUAGE), EINVAL},
{EFI_ENCODE_ERROR(EFI_COMPROMISED_DATA), EINVAL},
{EFI_ENCODE_ERROR(EFI_IP_ADDRESS_CONFLICT), EINVAL},
{EFI_ENCODE_ERROR(EFI_HTTP_ERROR), EIO},
{EFI_ENCODE_WARNING(EFI_WARN_UNKNOWN_GLYPH), EINVAL},
{EFI_ENCODE_WARNING(EFI_WARN_DELETE_FAILURE), EROFS},
{EFI_ENCODE_WARNING(EFI_WARN_WRITE_FAILURE), EROFS},
{EFI_ENCODE_WARNING(EFI_WARN_BUFFER_TOO_SMALL), E2BIG},
{EFI_ENCODE_WARNING(EFI_WARN_STALE_DATA), EINVAL},
{EFI_ENCODE_WARNING(EFI_WARN_FILE_SYSTEM), EROFS},
{EFI_ENCODE_WARNING(EFI_WARN_RESET_REQUIRED), EINTR},
};

static constexpr size_t UEFI_STATUS_ERRNO_MAP_LENGTH =
sizeof(UEFI_STATUS_ERRNO_MAP) / sizeof(UEFI_STATUS_ERRNO_MAP[0]);

static inline int uefi_status_to_errno(EFI_STATUS status) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should be LIBC_INLINE not static inline

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

for (size_t i = 0; i < UEFI_STATUS_ERRNO_MAP_LENGTH; i++) {
const struct errno_efi_status_entry *entry = &UEFI_STATUS_ERRNO_MAP[i];
if (entry->status == status)
return entry->errno_value;
}

// Unknown type
__builtin_unreachable();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably be given a defined value. These functions shouldn't be called with values out of range, but if it does happen it'll be easier to debug.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like we should trap here and the same for the function below for safety if possible.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

static inline EFI_STATUS errno_to_uefi_status(int errno_value) {
for (size_t i = 0; i < UEFI_STATUS_ERRNO_MAP_LENGTH; i++) {
const struct errno_efi_status_entry *entry = &UEFI_STATUS_ERRNO_MAP[i];
if (entry->errno_value == errno_value)
return entry->status;
}

// Unknown type
__builtin_unreachable();
}

} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_UEFI_ERROR_H
5 changes: 3 additions & 2 deletions libc/src/__support/OSUtil/uefi/exit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
//===-----------------------------------------------------------------===//

#include "src/__support/OSUtil/exit.h"
#include "include/Uefi.h"
#include "include/llvm-libc-types/EFI_SYSTEM_TABLE.h"
#include "src/__support/macros/config.h"

namespace LIBC_NAMESPACE_DECL {
namespace internal {

[[noreturn]] void exit(int status) {
efi_system_table->BootServices->Exit(efi_image_handle, status, 0, nullptr);
__llvm_libc_efi_system_table->BootServices->Exit(__llvm_libc_efi_image_handle,
status, 0, nullptr);
__builtin_unreachable();
}

Expand Down
16 changes: 11 additions & 5 deletions libc/src/__support/OSUtil/uefi/io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,34 @@

#include "io.h"

#include "Uefi.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/macros/config.h"

namespace LIBC_NAMESPACE_DECL {

ssize_t read_from_stdin(char *buf, size_t size) { return 0; }
ssize_t read_from_stdin([[gnu::unused]] char *buf,
[[gnu::unused]] size_t size) {
return 0;
}

void write_to_stdout(cpp::string_view msg) {
// TODO: use mbstowcs once implemented
for (size_t i = 0; i < msg.size(); i++) {
char16_t e[2] = {msg[i], 0};
efi_system_table->ConOut->OutputString(
efi_system_table->ConOut, reinterpret_cast<const char16_t *>(&e));
__llvm_libc_efi_system_table->ConOut->OutputString(
__llvm_libc_efi_system_table->ConOut,
reinterpret_cast<const char16_t *>(&e));
}
}

void write_to_stderr(cpp::string_view msg) {
// TODO: use mbstowcs once implemented
for (size_t i = 0; i < msg.size(); i++) {
char16_t e[2] = {msg[i], 0};
efi_system_table->StdErr->OutputString(
efi_system_table->StdErr, reinterpret_cast<const char16_t *>(&e));
__llvm_libc_efi_system_table->StdErr->OutputString(
__llvm_libc_efi_system_table->StdErr,
reinterpret_cast<const char16_t *>(&e));
}
}

Expand Down
49 changes: 49 additions & 0 deletions libc/startup/uefi/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# TODO: Use generic "add_startup_object" https://github.com/llvm/llvm-project/issues/133156
function(add_startup_object name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I apparently missed that each of the targets currently has their own definition of this. Please add a todo referencing #133156 (the bug I've filed for this)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

cmake_parse_arguments(
"ADD_STARTUP_OBJECT"
"ALIAS" # Option argument
"SRC" # Single value arguments
"DEPENDS;COMPILE_OPTIONS" # Multi value arguments
${ARGN}
)

get_fq_target_name(${name} fq_target_name)
if(ADD_STARTUP_OBJECT_ALIAS)
get_fq_deps_list(fq_dep_list ${ADD_STARTUP_OBJECT_DEPENDS})
add_library(${fq_target_name} ALIAS ${fq_dep_list})
return()
endif()

add_object_library(
${name}
SRCS ${ADD_STARTUP_OBJECT_SRC}
COMPILE_OPTIONS ${ADD_STARTUP_OBJECT_COMPILE_OPTIONS}
${ADD_STARTUP_OBJECT_UNPARSED_ARGUMENTS}
DEPENDS ${ADD_STARTUP_OBJECT_DEPENDS}
)
set_target_properties(
${fq_target_name}
PROPERTIES
OUTPUT_NAME ${name}.o
)
endfunction()

add_startup_object(
crt1
SRCS
crt1.cpp
DEPENDS
libc.src.__support.OSUtil.uefi.uefi_util
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To have an object file depend on object files (or a library), you'd need to use relocatable linking, see https://maskray.me/blog/2022-11-21-relocatable-linking. We use this approach for Linux crt0.o, see:

merge_relocatable_object(
crt1
.${LIBC_TARGET_ARCHITECTURE}.start
.${LIBC_TARGET_ARCHITECTURE}.tls
.do_start
)

Unfortunately, as far as I'm aware COFF linkers don't support relocatable linking so that approach isn't going to work. The most straightforward alternative is probably to move errno_to_uefi_status definition to the header.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, yeah I think for the purposes of this platform and the capabilities currently in lld-link, making it inline and in the header would be best.

)

add_custom_target(libc-startup)
set(startup_components crt1)
foreach(target IN LISTS startup_components)
set(fq_target_name libc.startup.uefi.${target})
add_dependencies(libc-startup ${fq_target_name})
install(FILES $<TARGET_OBJECTS:${fq_target_name}>
DESTINATION ${LIBC_INSTALL_LIBRARY_DIR}
RENAME $<TARGET_PROPERTY:${fq_target_name},OUTPUT_NAME>
COMPONENT libc)
endforeach()
30 changes: 30 additions & 0 deletions libc/startup/uefi/crt1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//===-- Implementation of crt for UEFI ------------------------------------===//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "include/llvm-libc-macros/stdlib-macros.h"
#include "include/llvm-libc-types/EFI_HANDLE.h"
#include "include/llvm-libc-types/EFI_STATUS.h"
#include "include/llvm-libc-types/EFI_SYSTEM_TABLE.h"
#include "src/__support/OSUtil/uefi/error.h"
#include "src/__support/macros/config.h"

extern "C" {
EFI_HANDLE __llvm_libc_efi_image_handle;
EFI_SYSTEM_TABLE *__llvm_libc_efi_system_table;

int main(int argc, char **argv, char **envp);

EFI_STATUS EfiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
__llvm_libc_efi_image_handle = ImageHandle;
__llvm_libc_efi_system_table = SystemTable;

// TODO: we need the EFI_SHELL_PROTOCOL, malloc, free, and UTF16 -> UTF8
// conversion.
return LIBC_NAMESPACE::errno_to_uefi_status(main(0, nullptr, nullptr));
}
}
Loading