Folkert van Verseveld
Folkert van Verseveld

Reputation: 51

When to use stdint.h's scalars in driver code

It has come to my attention that there seems no consistency nor best practise when to use native scalar types (integer, short, char) or the ones provided by stdint: uint32_t uint16_t uint8_t.

This is bugging me a lot because drivers form an essential part of a kernel that needs to be maintainable, consistent, stable and good.

Here is an illustrational example in gcc (used this for a hobby project for the raspberry pi):

// using native scalars
struct fbinfo {
        unsigned width, height;
        unsigned vwidth, vheight;
        unsigned pitch, bits;
        int x, y;
        void *ptr;
        unsigned size;
} __attribute__((aligned(16)));

// using stdint scalars
struct fbinfo {
        uint32_t width, height;
        uint32_t vwidth, vheight;
        uint32_t pitch, bits;
        int32_t x, y;
        uint32_t ptr; // convert to void* in order to use it
        uint32_t size;
} __attribute__((aligned(16)));

To me, the first example seems more logical, because this piece of code is only intended to run on a raspberry pi. It would be pointless to run this on other hardware.

The second example seems more practical, because it looks more descriptive since C does not guarantee much about the size of integers. It may be 16 bits or something else. uint32_t, uint_fast32_t and variants make guarantees about the exact or approximated size: e.g. at least or at most X bytes.

The operating system development community tends to use the stdint types, while the linux kernel uses multiple different techniques: u32, __u32, and endian specific stuff like __le32.

What considerations should be taken into account when to choose a scalar type and when to use a typedef'd scalar type? Is it better to use native scalar types in the provided example or use stdint.h's?

Upvotes: 4

Views: 2175

Answers (1)

ensc
ensc

Reputation: 6984

1. fixed with vs. fundamental types

Fixed width types are sometimes difficultly to use. E.g. printf() specifiers for int32_t are PRIi32 and require splitting of the format string:

printk("foo=" PRIi32 ", bar=" PRIi32 "\n", foo, bar);

Fixed width types should/must be used when hardware is accessed directly; e.g. when writing DMA descriptors. But for simple register accesses, writel() or readl() functions can be used which work with fundamental types.

As a rule of thumb, when a certain memory layout is assumed (like the __attribute__((__aligned__(16))) in your example, fixed width types should be used.

Signed fixed width types (int32_t x,y in your example) might need double checking, whether their representation matches the hardware expectations.

NOTE that in your example, the second structure is architecture dependent because of

    uint32_t ptr; // convert to void* in order to use it

Writing such thing in common C would be uintptr_t ptr and in the kernel it is common to write

    unsigned long ptr;

Alternatively, dma_addr_t might be a better type.

2. uint32_t vs. __u32

More than 10 years ago, Linus Torvalds objected against uint32_t because at this time, non-C99 compilers were common and using such types in (exported) linux headers would pollute the namespace.

But now, uint32_t and similar types are available everywhere (you can not compile the kernel with a non-C99 compiler) and kernel header export has been improved significantly, so these arguments are gone.

It is a matter of personal preference whether to use standard types or typedef'ed variants (which are framework dependent and differ between them).

3. uint_fastX_t and variants

They are not used in the kernel and I would avoid them. They combine disadvantages of uint32_t (difficult usage) and int (variable width).

4. __le32 vs. __u32

Use the endian types when specification explicitly requires them (e.g. in network protocol implementations). This makes it easy to detect wrong usage (e.g. assignments like endian_variable = native_variable).

Do not use them e.g. for filling processor structures (e.g. DMA descriptors); some processors can run both in little and big endian mode and native datatypes are usually the right way to write such infomration.

Upvotes: 5

Related Questions