libpldm
Error condition: An invalid state reached at runtime, caused either by resource exhaustion, or incorrect use of the library's public APIs and data types.
Invariant: A condition in the library's implementation that must never evaluate false.
Public API: Any definitions and declarations under include/libpldm
.
Wire format: Any message structure defined in the DMTF PLDM protocol specifications.
Resource exhaustion is always an error condition and never an invariant violation.
An invariant violation is always a programming failure of the library's implementation, and never the result of incorrect use of the library's public APIs (see error condition).
Corollaries of the above two points:
Incorrect use of public API functions is always an error condition, and is dealt with by returning an error code.
Incorrect use of static functions in the library's implementation is an invariant violation which may be established using assert()
.
assert()
is the recommended way to demonstrate invariants are upheld.
--- title: libpldm symbol lifecycle --- stateDiagram-v2 direction LR [*] --> Testing: Add Testing --> Testing: Change Testing --> [*]: Remove Testing --> Stable: Stabilise Stable --> Deprecated: Deprecate Deprecated --> [*]: Remove
The ABI of the library produced by the build is controlled using the abi
meson option. The following use cases determine how the abi
option should be specified:
Use Case | Meson Configuration |
---|---|
Production | -Dabi=deprecated,stable |
Maintenance | -Dabi=stable |
Development | -Dabi=deprecated,stable,testing |
Applications and libraries that depend on libpldm
can identify how to migrate off of deprecated APIs by constraining the library ABI to the stable category. This will force the compiler identify any call-sites that try to link against deprecated symbols.
Applications and libraries often require functionality that doesn't yet exist in libpldm
. The work is thus in two parts:
libpldm
libpldm
in the dependent application or libraryAdding APIs to a library is a difficult task. Generally, once an API is exposed in the library's ABI, any changes to the API risk breaking applications already making use of it. To make sure we have more than one shot at getting an API right, all new APIs must first be exposed in the testing category. Concretely:
Patches adding new APIs MUST mark them as testing and MUST NOT mark them as stable.
Three macros are provided through config.h
(automatically included for all translation units) to mark functions as testing, stable or deprecated:
LIBPLDM_ABI_TESTING
LIBPLDM_ABI_STABLE
LIBPLDM_ABI_DEPRECATED
These annotations go immediately before your function signature:
LIBPLDM_ABI_TESTING pldm_requester_rc_t pldm_transport_send_msg(struct pldm_transport *transport, pldm_tid_t tid, const void *pldm_req_msg, size_t req_msg_len) { ... }
Marking a function as stable makes the following promise to users of the library:
We will not remove or change the symbol name, argument count, argument types, return type, or interpretation of relevant values for the function before first marking it as
LIBPLDM_ABI_DEPRECATED
and then subsequently creating a tagged release
Marking a function as stable does not promise that it is free of implementation bugs. It is just a promise that the prototype won't change without notice.
Given this, it is always okay to implement functions marked stable in terms of functions marked testing inside of libpldm. If we remove or change the prototype of a function marked testing the only impact is that we need to fix up any call sites of that function in the same patch.
To move a function from the testing category to the stable category, it's required that patches demonstrating use of the function in a dependent application or library be linked in the commit message of the stabilisation change. We require this to demonstrate that the implementer has considered its use in context before preventing us from making changes to the API.
Meson is broadly used in the OpenBMC ecosystem, the historical home of libpldm
. Meson's subprojects are a relatively painless way of managing dependencies for the purpose of developing complex applications and libraries. Use of libpldm
as a subproject is both supported and encouraged.
libpldm
's ABI can be controlled from a parent project through meson's subproject configuration syntax:
meson setup ... -Dlibpldm:abi=deprecated,stable,testing ...
[ ] All publicly exposed macros, types and functions relating to the PLDM specifications must be prefixed with either pldm_
or PLDM_
as appropriate
encode_*()
and decode_*()
function symbols[ ] All publicly exposed macros, types and functions relating to the library implementation must be prefixed with libpldm_
or LIBPLDM_
[ ] All pldm_
-prefixed symbols must also name the related specification. For example, for DSP0248 Platform Monitoring and Control, the symbol prefix should be pldm_platform_
.
[ ] All enum members must be prefixed with the type name
[ ] If I've added support for a new PLDM message type, then I've defined both the encoder and decoder for that message.
[ ] I've designed my APIs so their implementation does not require heap allocation.
[ ] My new public message codec functions take a struct
representing the message as a parameter
[ ] Each new struct
I've defined is used in at least one new function I've added to the public API.
[ ] My new public struct
definitions are not marked __attribute__((packed))
[ ] My new public struct
definitions do not define a flexible array member, unless:
[ ] It's contained in an #ifndef __cplusplus
macro guard, as flexible arrays are not specified by C++, and
[ ] I've implemented an accessor function so the array base pointer can be accessed from C++, and
[ ] It is defined as per the C17 specification by omitting the length[^1]
[ ] I've annotated the flexible array member with LIBPLDM_CC_COUNTED_BY()
[^1]: C17 draft specification, 6.7.2.1 Structure and union specifiers, paragraph 18.
[ ] My new function symbols are marked with LIBPLDM_ABI_TESTING
in the implementation
[ ] I've guarded the test cases of functions marked LIBPLDM_ABI_TESTING
so that they are not compiled when the corresponding function symbols aren't visible
[ ] All my error conditions are handled by returning an error code to the caller.
[ ] All my invariants are tested using assert()
.
[ ] I have not used assert()
to evaluate any error conditions without also handling the error condition by returning an error code the the caller.
assert()
disabled (-Db_ndebug=if-release
, which provides -DNDEBUG
in CFLAGS
).[ ] My new APIs return negative errno
values on error and not PLDM completion codes.
[ ] If my work interacts with the PLDM wire format, then I have done so using the msgbuf
APIs found in src/msgbuf.h
(and under src/msgbuf/
) to minimise concerns around spatial memory safety and endian-correctness.
[ ] I've used goto
to clean up resources acquired prior to encountering an error condition
[ ] I've released acquired resources in stack-order
[ ] I've declared variables in reverse-christmas-tree (inverted pyramid) order in any block scopes I've added or changed.
[ ] If I've added support for a new message type, then my commit message specifies all of:
[ ] If my work impacts the public API of the library, then I've added an entry to CHANGELOG.md
describing my work
[ ] I've documented the wire format for all OEM messages under docs/oem/${OEM_NAME}/
[ ] I've added public OEM API declarations and definitions under include/libpldm/oem/${OEM_NAME}/
, and installed them to the same relative location.
[ ] I've implemented the public OEM APIs under src/oem/${OEM_NAME}/
[ ] I've implemented the OEM API tests under tests/oem/${OEM_NAME}/
The ${OEM_NAME}
folder must be created with the name of the OEM/vendor in lower case.
Finally, the OEM name must be added to the list of choices for the oem
meson option, and the meson.build
files updated throughout the tree to guard integration of the OEM extensions.
[ ] The API of interest is currently marked LIBPLDM_ABI_TESTING
[ ] My commit message links to a publicly visible patch that makes use of the API
[ ] My commit updates the annotation from LIBPLDM_ABI_TESTING
to LIBPLDM_ABI_STABLE
only for the function symbols demonstrated by the patch linked in the commit message.
[ ] I've removed guards from the function's tests so they are always compiled
[ ] If I've updated the ABI dump, then I've used the OpenBMC CI container to do so.
To update the ABI dump you'll need to build an appropriate OpenBMC CI container image of your own. Some hints on how to do this locally can be found in the openbmc/docs repository. You can list your locally built images with docker images
.
Assuming:
export OPENBMC_CI_IMAGE=openbmc/ubuntu-unit-test:2024-W21-ce361f95ff4fa669
the ABI dump can be updated with:
docker run \ --cap-add=sys_admin \ --rm=true \ --privileged=true \ -u $USER \ -w $(pwd) \ -v $(pwd):$(pwd) \ -e MAKEFLAGS= \ -t $OPENBMC_CI_IMAGE \ ./scripts/abi-dump-updater
[ ] If the function is marked LIBPLDM_ABI_TESTING
, then I have removed it
[ ] If the function is marked LIBPLDM_ABI_STABLE
, then I have changed the annotation to LIBPLDM_ABI_DEPRECATED
and left it in-place.
[ ] If the function is marked LIBPLDM_ABI_DEPRECATED
, then I have removed it only after satisfying myself that each of the following is true:
libpldm
subsequent to the API being marked deprecatedA change to an API is a pure rename only if there are no additional behavioural changes. Renaming an API with no other behavioural changes is really two actions:
[ ] Only the name of the function has changed. None of its behaviour has changed.
[ ] Both the new and the old functions are declared in the public headers
[ ] I've aliased the old function name to the new function name via the libpldm_deprecated_aliases
list in meson.build
[ ] I've added a semantic patch to migrate users from the old name to the new name