1. Coding Standard

This document describes the coding rules to follow to contribute to the project.

1.1. General

The following coding standard is derived from MISRA C:2012 Guidelines, TF-A coding style and Linux kernel coding style coding standards.

1.2. File Encoding

The source code must use the UTF-8 character encoding. Comments and documentation may use non-ASCII characters when required (e.g. Greek letters used for units) but code itself is still limited to ASCII characters.

1.3. Language

The primary language for comments and naming must be International English. In cases where there is a conflict between the American English and British English spellings of a word, the American English spelling is used.

Exceptions are made when referring directly to something that does not use international style, such as the name of a company. In these cases the existing name should be used as-is.

1.4. C Language Standard

The C language mode used for RMM is GNU11. This is the “GNU dialect of ISO C11”, which implies the ISO C11 standard with GNU extensions.

Both GCC and Clang compilers have support for GNU11 mode, though Clang does lack support for a small number of GNU extensions. These missing extensions are rarely used, however, and should not pose a problem.

1.5. Length

  • Each file, function and scopes should have a logical uniting theme.

    No length limit is set for a file.

  • A function should be 24 lines maximum.

    This will not be enforced, any function being longer should trigger a discussion during the review process.

  • The recommended maximum line length is 80 characters, except for string literals as it would make any search for it more difficult. A maximum length of 100 characters is enforced by the coding guidelines static check.

  • A variable should not be longer than 31 characters.

    Although the C11 specification specifies that the number of signitificant characters in an identifier is implementation defined it sets the translation limit to the 31 initial characters.

TYPE

LIMIT

function

24 lines (not enforced)

line

100 characters

identifier

31 characters

1.6. Headers/Footers

  • Include guards:

#ifndef FILE_NAME_H
#define FILE_NAME_H

<header content>

#endif /* FILE_NAME_H */
  • Include statement variant is <>:

#include <file.h>
  • Include files should be alphabetically ordered:

#include <axxxx.h>
#include <bxxxx.h>
[...]
#include <zxxxx.h>
  • If possible, use forward declaration of struct types in public headers. This will reduce interdependence of header file inclusion.

#include <axxxx.h>
#include <bxxxx.h>
[...]
/* forward declaration */
struct x;
void foo(struct *x);

1.7. Naming conventions

  • Case: Functions and variables must be in Snake Case

unsigned int my_snake_case_variable = 0U;

void my_snake_case_function(void)
{
        [...]
}
  • Local variables should be declared at the top of the closest opening scope and should be short.

    We won’t enforce a length, and defining short is difficult, this motto (from Linux) catches the spirit

    LOCAL variable names should be short, and to the point.

    If you have some random integer loop counter, it should probably be called i.

    Calling it loop_counter is non-productive, if there is no chance of it being mis-understood.

    Similarly, tmp can be just about any type of variable that is used to hold a temporary value.

    If you are afraid to mix up your local variable names, you have another problem.

int foo(const int a)
{
        int c; /* needed in the function */
        c = a; /* MISRA-C rules recommend to not modify arguments variables */

        if (c == 42) {
                int b; /* needed only in this "if" statment */

                b = bar(); /* bar will return an int */
                if (b != -1) {
                        c += b;
                }
        }
        return c;
}
  • Use an appropraite prefix for public API of a component. For example, if the component name is bar, then the init API of the component should be called bar_init().

1.8. Indentation

Use tabs for indentation. The use of spaces for indentation is forbidden except in the case where a term is being indented to a boundary that cannot be achieved using tabs alone.

Tab spacing should be set to 8 characters.

Trailing whitespaces or tabulations are not allowed and must be trimmed.

1.9. Spacing

Single spacing should be used around most operators, including:

  • Arithmetic operators (+, -, /, *, %)

  • Assignment operators (=, +=, etc)

  • Boolean operators (&&, ||)

  • Comparison operators (<, >, ==, etc)

  • Shift operators (>>, <<)

  • Logical operators (&, |, etc)

  • Flow control (if, else, switch, while, return, etc)

No spacing should be used around the following operators

  • Cast (())

  • Indirection (*)

1.10. Braces

  • Use K&R style for statements.

  • Function opening braces are on a new line.

  • Use braces even for singled line.

void function(void)
{
        /* if statement */
        if (my_test) {
                do_this();
                do_that();
        }

        /* if/else statement */
        if (my_Test) {
                do_this();
                do_that();
        } else {
                do_other_this();
        }
}

1.11. Commenting

Double-slash style of comments (//) is not allowed, below are examples of correct commenting.

/*
 * This example illustrates the first allowed style for multi-line comments.
 *
 * Blank lines within multi-lines are allowed when they add clarity or when
 * they separate multiple contexts.
 */
/**************************************************************************
 * This is the second allowed style for multi-line comments.
 *
 * In this style, the first and last lines use asterisks that run the full
 * width of the comment at its widest point.
 *
 * This style can be used for additional emphasis.
 *************************************************************************/
/* Single line comments can use this format */
/***************************************************************************
 * This alternative single-line comment style can also be used for emphasis.
 **************************************************************************/

1.12. Error return values and Exception handling

  • Function return type must be explicitly defined.

  • Unless specifed otherwise by an official specification, return values must be used to return success or failure (Standard Posix error codes).

    Return an integer if the function is an action or imperative command

    Failure: -Exxx (STD posix error codes, unless specified otherwise)

    Success: 0

    Return a boolean if the function is as predicate

    Failure: false

    Success: true

  • If a function returns error information, then that error information shall be tested.

    Exceptions are allowed for STDLIB functions (memcpy/printf/…) in which case it must be void casted.

#define MY_TRANSFORMED_ERROR  (-1)

void my_print_function(struct my_struct in_mystruct)
{
        long long transformed_a = my_transform_a(in_mystruct.a);

        if (transform_a != MY_TRANSFORMED_ERROR) {
                (void)printf("STRUCT\n\tfield(a): %ll\n", transformed_a);
        } else {
                (void)printf("STRUCT\n\tERROR %ll\n", transformed_a);
        }
}

1.13. Use of asserts and panic

Assertions, as a general rule, are only used to catch errors during development cycles and are removed from production binaries. They are useful to document pre-conditions for a function or impossible conditions in code. They are not substitutes for proper error checking and any expression used to test an assertion must not have a side-effect.

For example,

assert(--i == 0);

should not be used in code.

Assertions can be used to validate input arguments to an API as long as the caller and callee are within the same trust boundary.

panic() is used in places wherein it is not possible to continue the execution of program sensibly. It should be used sparingly within code and, if possible, instead of panic(), components should return error back to the caller and the caller can decide on the appropriate action. This is particularly useful to build resilence to the program wherein non-functional part of the program can be disabled and, if possible, other functional aspects of the program can be kept running.

1.14. Using COMPILER_ASSERT to check for compile time data errors

Where possible, use the COMPILER_ASSERT macro to check the validity of data known at compile time instead of checking validity at runtime, to avoid unnecessary runtime code.

For example, this can be used to check that the assembler’s and compiler’s views of the size of an array is the same.

#include <utils_def.h>

define MY_STRUCT_SIZE 8 /* Used by assembler source files */

struct my_struct {
    uint32_t arg1;
    uint32_t arg2;
};

COMPILER_ASSERT(MY_STRUCT_SIZE == sizeof(struct my_struct));

If MY_STRUCT_SIZE in the above example were wrong then the compiler would emit an error like this:

my_struct.h:10:1: note: in expansion of macro 'COMPILER_ASSERT'
 10 | COMPILER_ASSERT(MY_STRUCT_SIZE == sizeof(struct my_struct));
    | ^~~~~~~~~~~~~~~

1.15. Data types, structures and typedefs

  • Data Types:

The RMM codebase should be kept as portable as possible for 64-bits platforms. To help with this, the following data type usage guidelines should be followed:

  • Where possible, use the built-in C data types for variable storage (for example, char, int, long long, etc) instead of the standard C11 types. Most code is typically only concerned with the minimum size of the data stored, which the built-in C types guarantee.

  • Avoid using the exact-size standard C11 types in general (for example, uint16_t, uint32_t, uint64_t, etc) since they can prevent the compiler from making optimizations. There are legitimate uses for them, for example to represent data of a known structure. When using them in a structure definition, consider how padding in the structure will work across architectures.

  • Use int as the default integer type - it’s likely to be the fastest on all systems. Also this can be assumed to be 32-bit as a consequence of the Procedure Call Standard for the Arm 64-bit Architecture .

  • Avoid use of short as this may end up being slower than int in some systems. If a variable must be exactly 16-bit, use int16_t or uint16_t.

  • long are defined as LP64 (64-bit), this is guaranteed to be 64-bit.

  • Use char for storing text. Use uint8_t for storing other 8-bit data.

  • Use unsigned for integers that can never be negative (counts, indices, sizes, etc). RMM intends to comply with MISRA “essential type” coding rules (10.X), where signed and unsigned types are considered different essential types. Choosing the correct type will aid this. MISRA static analysers will pick up any implicit signed/unsigned conversions that may lead to unexpected behaviour.

  • For pointer types:

    • If an argument in a function declaration is pointing to a known type then simply use a pointer to that type (for example: struct my_struct *).

    • If a variable (including an argument in a function declaration) is pointing to a general, memory-mapped address, an array of pointers or another structure that is likely to require pointer arithmetic then use uintptr_t. This will reduce the amount of casting required in the code. Avoid using unsigned long or unsigned long long for this purpose; it may work but is less portable.

    • Use of void * is generally discouraged. Although it is useful to represent pointers to types that are abstracted away from the callers and has useful implicit cast properties, for the sake of a more uniform code base, we encourage use of uintptr_t where possible.

    • Avoid pointer arithmetic generally (as this violates MISRA C 2012 rule 18.4) and especially on void pointers (as this is only supported via language extensions and is considered non-standard). In RMM, setting the W build flag to W=3 enables the -Wpointer-arith compiler flag and this will emit warnings where pointer arithmetic is used.

    • Use ptrdiff_t to compare the difference between 2 pointers.

  • Use size_t when storing the sizeof() something.

  • Use ssize_t when returning the sizeof() something from a function that can also return an error code; the signed type allows for a negative return code in case of error. This practice should be used sparingly.

  • Use uint64_t to store the contents of an AArch64 register or represent a 64-bit value. Use of unsigned long or u_register_t for these purposes is discouraged.

These guidelines should be updated if additional types are needed.

  • Typedefs:

Typedef should be avoided and used only to create opaque types. An opaque data type is one whose concrete data structure is not publicly defined. Opaque data types can be used on handles to resources that the caller is not expected to address directly.

/* File main.c */
#include <my_lib.h>

int main(void)
{
      context_t       *context;
      int             res;

      context = my_lib_init();

      res = my_lib_compute(context, "2x2");
      if (res == -EMYLIB_ERROR) {
              return -1
      }

      return res;
}
/* File my_lib.h */
#ifndef MY_LIB_H
#define MY_LIB_H

typedef struct my_lib_context {
  [...] /* whatever internal private variables you need in my_lib */
} context_t;

#endif /* MY_LIB_H */

1.16. Macros and Enums

  • Favor functions over macros.

  • Preprocessor macros and enums values are written in all uppercase text.

  • A numerical value shall be typed.

/* Common C usage​ */
#define MY_MACRO 4UL

/* If used in C and ASM (included from a .S file)​ */
#define MY_MACRO UL(4)
  • Expressions resulting from the expansion of macro parameters must be enclosed in parentheses.

  • A macro parameter immediately following a # operator mustn’t be immediately followed by a ## operator.

#define SINGLE_HASH_OP(x)            (#x)            /* allowed */
#define SINGLE_DOUBLE_HASH_OP(x, y)  (x ## y)        /* allowed */
#define MIXED_HASH_OP(x, y)          (#x ## y)       /* not allowed */
  • Avoid defining macros that affect the control flow (i.e. avoid using return/goto in a macro).

  • Macro with multiple statements can be enclosed in a do-while block or in a expression statement.

int foo(char **b);

#define M1(a, b)                      \
        do {                          \
              if ((a) == 5) {         \
                      foo((b));       \
              }                       \
        } while (false)

#define M2(a, b)              \
        ({                    \
        if ((a) == 5) {       \
                foo((b));     \
        }                     \
        })

int foo(char **b)
{
        return 42;
}

int main(int ac, char **av)
{
        if (ac == 1) {
                M1(ac, av);
        } else if (ac == 2) {
                M2(ac, av);
        } else {
                return -1;
        }

        return ac;
}

1.17. Switch statements

  • Return in a case are allowed.

  • Fallthrough are allowed as long as they are commented.

  • Do not rely on type promotion between the switch type and the case type.

1.18. Inline assembly

  • Favor C language over assembly language.

  • Document all usage of assembly.

  • Do not mix C and ASM in the same file.

1.19. Libc functions that are banned or to be used with caution

Below is a list of functions that present security risks.

libc function

Comments

strcpy, wcscpy, strncpy

use strlcpy instead

strcat, wcscat, strncat

use strlcat instead

sprintf, vsprintf

use snprintf, vsnprintf instead

snprintf

if used, ensure result fits in buffer i.e : snprintf(buf,size…) < size

vsnprintf

if used, inspect va_list match types specified in format string

strtok, strtok_r, strsep

Should not be used

ato*

Should not be used

*toa

Should not be used

The use of above functions are discouraged and will only be allowed in justified cases after a discussion has been held either on the mailing list or during patch review and it is agreed that no alternative to their use is available. The code containing the banned APIs must properly justify their usage in the comments.

The above restriction does not apply to Third Party IP code inside the ext/ directory.