Gionata Benelli
Gionata Benelli

Reputation: 407

How to share data-structure definition between kernel modules and user-application?

I would like to develop a device-driver on linux(written in C) and a user-space library wrapping all functions provided by my device-driver (also written in C). Just to make it more clear, my library wil provide the following methods:

The function will use the file associated to my device-driver, in particular:

assume myConfStruct is a simple structure containing something like this:

typedef struct {
    uint16_t var1;
    uint8_t var2;
} myConfStruct;

I would like the myConfStruct to be a structure shared between both my user-application (library) and my kernel-driver using a single header. Are there any best-practice while doing this? I would like to have the structure defined into only one file, having it defined in multiple files seems to be quite error-prone if i plan on changing it in the future, but I understood that I should not include <linux/types.h> inside my user files and I shouldn't use <stdint.h> inside my device-driver. So another question is also, how can I define the interface between a module and the user-application so that who is implementing the application is not forced to include any linux header?

Upvotes: 2

Views: 797

Answers (1)

Blabbo the Verbose
Blabbo the Verbose

Reputation: 56

What you are creating is a character device. The kernel documentation includes a specific section, the Linux driver implementer's guide, you should also read. Specifically, the ioctl based interfaces section, which also describes some of the considerations necessary (regarding alignment and 64-bit fields).

As to header files, see KernelHeaders article at kernelnewbies.org.

I would like to have the structure defined into only one file, having it defined in multiple files seems to be quite error-prone if i plan on changing it in the future.

No. You do specify the headers in two separate files: one for use in-kernel, and the other for use by userspace.

Kernel-userspace interface should be stable. You should take care to design your data structure so that you can extend it if necessary; preferably by adding some padding reserved for future use and required to be initialized to zero, and/or a version number at the beginning of the structure. Later versions must support all previous versions of the structure as well. Even if it is only a "toy" or "experimental" device driver, it is best to learn to do it right from the get go. This stuff is much, much harder to learn to "add afterwards"; I'm talking from deep experience here.

As a character device, you should also be prepared for the driver to be compiled on other architectures besides the one you are developing on. Even byte order ("endianness") can vary, although all Linux architectures are currently either ILP32 or LP64.

Also remember that there are several hardware architectures, including x86-64, that support both 64-bit and 32-bit userspace. So, even if you believe your driver will ever be used on x86-64, you cannot really assume the userspace is 64-bit (and not 32-bit). Look at existing code to see how it is done right; I recommend using e.g. bootlin's elixir to browse the Linux kernel sources.

Kernel-side header file should use __s8, __u8, __s16, __u16, __s32, __u32, __s64, or __u64. For pointers, use __u64, and u64_to_user_ptr().

Userspace-side header file should use <stdint.h> types (int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t) and uint64_t for pointers. Use a cast via (uintptr_t) for the conversion, i.e. ptr = (void *)(uintptr_t)u64; and u64 = (uintptr_t)ptr.

Ensure all members are naturally aligned. This means that an N-bit member is preceded by k×N bits of other members, where k is either zero or a positive integer. Thus, if your structure needs an unsigned and a signed 8-bit integer (one for version, one for foo), an 16-bit signed integer (bar), and a 64-bit unsigned integer (baz), considering the version should be first, you'll probably want

struct kernel_side {
    __u8   version;
    __s8   foo;
    __u16  bar;
    __u32  padding;
    __u64  baz;
};

struct userspace_side {
    uint8_t   version;
    int8_t    foo;
    uint16_t  bar;
    uint32_t  padding;
    uint64_t  baz;
};

You can also have character arrays and such, but do note that a single ioctl data block is limited to 8191 bytes or less in length.

If you spend some time designing your interface structures, you'll find that careful design will avoid annoying issues like compat_ support (making them just simple wrappers). Personally, I end up creating a test version with a userspace test program to see what works best, and only then decide on the data structures.

Upvotes: 3

Related Questions