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.