irrelevant


Implementation encapsulation using container_of

The container_of macro is used a lot in the Linux kernel. I'm also using it a lot in my projects, but often when I collaborate, quetions tend to come up about why and what it is doing. There are plenty of online resources that describe how the macro works, so I won't be repeating that. I'd rather talk about where it makes sense to use it for library development.

When we added iommufd support in libvfn, we needed to have it coexist side-by-side with the existing vfio backend. Since we want a common API that doesn't expose what the underlying backend is, we want to pass around a type that does not expose those details. That is the struct iommu_ctx.

struct iommu_ctx {
        struct iova_map map;
        struct iommu_ctx_ops ops;

        /* ... */
};

Now, the two backends, vfio and iommufd, each define their own internal type to keep their own state.

struct iommu_ioas {
        struct iommu_ctx ctx;

        char *name;
        uint32_t id;
};

struct vfio_container {
        struct iommu_ctx ctx;
        char *name;

        int fd;

        /* ... */
};

Notice how these types embed the struct iommu_ctx. Instead of having a void pointer to the implementation, we reverse the relationship and have the implementation embed the generic stuff. We provide a function to get an iommu context,

struct iommu_ctx *iommu_get_context(const char *name)
{
#ifdef HAVE_VFIO_DEVICE_BIND_IOMMUFD
        if (__iommufd_broken)
                goto fallback;

        return iommufd_get_iommu_context(name);

fallback:
#endif
        return vfio_get_iommu_context(name);
}

We have some compile guards here because iommufd may not always be available within the running kernel. Additionally, the configuration may be "broken" or misconfigured, so we have to support a fallback to the "legacy" vfio implementation.

For iommufd, the function looks like this.

struct iommu_ctx *iommufd_get_iommu_context(const char *name)
{
        struct iommu_ioas *ioas = znew_t(struct iommu_ioas, 1);

        iommu_ctx_init(&ioas->ctx);

        ioas->name = strdup(name);

        return &ioas->ctx;
}

The key here is returning a pointer to the embedded struct. Later, when the iommufd specific functions are being called, like iommu_ioas_do_dma_map(), the function retrieves the original struct using container_of.

static int iommu_ioas_do_dma_map(struct iommu_ctx *ctx, void *vaddr,
                                 size_t len, uint64_t *iova,
                                 unsigned long flags)
{
        struct iommu_ioas *ioas = container_of_var(ctx, ioas, ctx);

        /* ... */
}

The container_of macro is super useful for encapsulating implementation without resorting to exposing implementation in the public API. Remember, without this, we would probably a void *impl member, or similar, on the struct iommu_ctx, which unnecessarily exposes internal and irrelevant details.