Discovery and Enumeration#

UDDF loads driver libraries (.so files) at runtime through dynamic linking. A driver library is a shared library that contains one or more driver implementations. Each library exports a single C-linkage entry point, and through that entry point the library advertises the drivers it contains so UDDF can instantiate them on demand.

The UDDF Driver Model page introduced a minimal single-driver enumerator for the CAM123 example. This page covers the discovery mechanism in detail, including how to bundle multiple drivers in a single library.

The Entry Point#

Every UDDF driver library must export exactly one C-linkage function:

extern "C" uddf::ddi::DriverEnumeratorPtr uddf_discover_drivers();

When UDDF loads your shared library, it looks up this symbol and calls it. You return a DriverEnumeratorPtr (a std::unique_ptr<IDriverEnumerator>) that UDDF takes ownership of. UDDF then queries the enumerator to discover and create the drivers your library provides.

The handshake is straightforward:

  1. UDDF loads your .so through dlopen (or platform equivalent).

  2. UDDF resolves the uddf_discover_drivers symbol.

  3. UDDF calls the function and takes ownership of the returned enumerator.

  4. UDDF iterates the enumerator to discover available drivers and creates them as needed.

DriverInfo#

Each driver in your library is described by a DriverInfo structure (uddf/ddi/discovery.hpp):

struct DriverInfo {
    std::string name;        // Human-readable driver name
    std::string description; // Short description of the driver
    std::string vendor;      // Vendor or author
    std::string revision;    // Version string
};

The name field is how UDDF identifies your driver to the consumer. Choose a name that uniquely identifies the hardware or functionality the driver supports. The description field provides a brief summary for diagnostic and logging purposes. The vendor and revision fields are metadata that UDDF exposes but does not interpret.

IDriverEnumerator#

IDriverEnumerator is the interface your library implements to advertise its drivers:

class IDriverEnumerator {
public:
    virtual ~IDriverEnumerator() = default;
    virtual std::string_view GetName() const = 0;
    virtual size_t GetDriverCount() const = 0;
    virtual const DriverInfo* GetDriverInfo(size_t index) const = 0;
    virtual std::unique_ptr<IDriver> CreateDriver(size_t index) = 0;
};

The methods:

GetName()

Returns a library-level name used for bookkeeping and logging. This is not the same as an individual driver name.

GetDriverCount()

Returns the number of distinct drivers this library provides. This value defines the valid index range for the two methods below.

GetDriverInfo(index)

Returns a non-owning pointer to the DriverInfo for the driver at the given zero-based index. Returns nullptr if the index is out of range.

CreateDriver(index)

Factory method that creates and returns a new IDriver instance for the given index. UDDF takes ownership of the returned pointer. Returns nullptr if creation fails.

Multi-Driver Libraries#

A single shared library can contain any number of drivers. This is where the index-based enumeration model pays off. There are two common patterns.

Different Driver Classes#

A library can bundle several distinct driver classes under one .so. For example, a GMSL camera library might contain a module driver, a deserializer driver, and power drivers for each. Each index maps to a different IDriver subclass:

std::unique_ptr<IDriver> CreateDriver(size_t index) override {
    switch (index) {
    case 0: return std::make_unique<ModuleDriver>();
    case 1: return std::make_unique<DeserializerDriver>();
    case 2: return std::make_unique<ModulePowerDriver>();
    case 3: return std::make_unique<DeserializerPowerDriver>();
    default: return nullptr;
    }
}

Each index has its own DriverInfo with a distinct name and description. This pattern keeps related drivers together in a single deployable unit.

Driver Variations#

The same IDriver class can appear multiple times with different DriverInfo metadata and constructor arguments. This lets you ship hardware variants without duplicating code:

std::unique_ptr<IDriver> CreateDriver(size_t index) override {
    switch (index) {
    case 0: return std::make_unique<CoEDriver>("StandardConfig");
    case 1: return std::make_unique<CoEDriver>("VariantConfig_X");
    default: return nullptr;
    }
}

Each variation has its own DriverInfo (different name, different revision) even though they share the same underlying driver class. The consumer selects the appropriate variation by matching against the DriverInfo metadata.

Note

For libraries with many drivers, a table-driven pattern can be cleaner than a switch statement. Store an array of {DriverInfo, factory_function} pairs and index into it directly from GetDriverInfo and CreateDriver.

UDDF PDK - Discovery and Enumeration