GMSL Building Blocks (UBB)#
UDDF Building Blocks (UBB) is an optional convenience layer for GMSL camera module drivers. It reduces the boilerplate required to implement the standard IGmslModuleControl and IModuleComponentAccess interfaces by letting you compose a module driver from reusable component classes: one for the sensor, one for the serializer, and optionally one for an EEPROM.
UBB does not add new behavior. Everything it does is achievable with the raw UDDF DDI interfaces documented in GMSL. If UBB’s structure does not fit your module, use the DDI interfaces directly. Any customer can build an equivalent layer.
Note
The UDDF DDI interfaces are the authoritative API. UBB is a convenience wrapper that you are free to use, modify, or ignore.
When to Use UBB#
Use UBB when your camera module follows the standard GMSL pattern—one serializer, one or more sensors, and optional EEPROMs—and you want to reduce driver boilerplate. UBB handles interface routing, component enumeration, device table aggregation, and lifecycle ordering so that you can focus on hardware-specific logic.
Use the raw UDDF DDI interfaces (as described in GMSL and UDDF Driver Model) for the following use cases:
You need non-standard interface routing from
GetInterface().You require multiple serializers on a single module.
UBB’s fixed lifecycle ordering does not match your hardware requirements.
UBB and raw UDDF are not mutually exclusive at the project level. Some modules in a driver library can use UBB while others use raw DDI. If your module includes components beyond the standard three—such as an illuminator, IMU, or PMIC—you can create your own UBB component classes. Refer to Extending UBB.
What UBB Provides#
UBB automates the following tasks that every standard GMSL module driver must handle:
GetInterface()routing forIGmslModuleControlandIModuleComponentAccess.Component registration and type-based enumeration through
GetComponent()andGetComponentCount().Device table and GPIO pin table aggregation from all component objects.
Lifecycle orchestration: initialization in creation order, deinitialization in reverse order.
StartStreamingandStopStreamingdelegation to sensor components.Per-component error logging when a lifecycle method fails.
What UBB Does Not Provide#
No deserializer or power-driver wrappers. Those driver types remain raw DDI.
No new DDI interfaces. Each UBB class exposes only the standard interfaces documented in GMSL.
No additional configuration beyond the standard
GmslModuleContext::Config.
Architecture#
The following diagram shows how UBB classes relate to the UDDF framework and DDI interfaces. Your driver code (shown in teal at the bottom) derives from the UBB classes and inherits all of the wiring automatically.
ModuleUbb inherits from IDriver and owns a collection of DeviceUbb-derived component objects. You register each component by type (MODULE_COMPONENT_SENSOR, MODULE_COMPONENT_SERIALIZER, or MODULE_COMPONENT_EEPROM). The driver routes GetInterface() calls for IGmslModuleControl and IModuleComponentAccess to itself. Individual components route their own interface queries – for example, a SensorUbb returns ICameraSensorControl and ICameraSensorInfo when asked.
Lifecycle Hooks#
UBB follows the standard IGmslModuleControl lifecycle documented in GMSL. It calls your component DDI methods (Configure(), ProbeHardware(), Init(), Deinit(), StartStreaming(), StopStreaming()) automatically in creation order (reverse for Deinit). The table below lists the hooks where you can inject your own logic.
Lifecycle Phase |
Your Hook |
When to Use |
|---|---|---|
ConfigureDriver |
|
Instantiate your component UBB objects and register them with |
ProbeHardware |
|
Perform pre-probe work such as serializer GPIO setup before delegating to the base class. |
Init (before components) |
|
Run cross-component logic before individual component |
Init (after components) |
|
Run cross-component logic after all components have initialized. |
Reset, Authenticate, BeforePowerOff, AfterPowerOn |
Override in your module driver |
Default implementations return |
Writing a Module Driver with UBB#
This section builds on the CAM123 example from UDDF Driver Model, showing how to restructure it using UBB.
Step 1 – Create Component Classes#
Derive one class per component from the appropriate UBB base. Every UBB component requires four methods: GetName(), Configure(), GetDeviceTable(), and GetGpioPinTable(). Beyond those, you implement the same DDI methods you would in a raw UDDF driver (refer to GMSL for the full interface definitions).
class Cam123Sensor : public gmslubb::SensorUbb {
public:
explicit Cam123Sensor(GmslModuleContext::Config const& config)
: SensorUbb(config) {}
const char* GetName() const override { return "CAM123-sensor"; }
bool Configure(GmslModuleContext::Config const& config) override {
return true;
}
uddf::ddi::DeviceTable GetDeviceTable() const override {
return {{ .i2cAddress = 0x42, .offsetWidth = 2, .dataWidth = 1 }};
}
uddf::ddi::GpioPinTable GetGpioPinTable() const override { return {}; }
// Lifecycle and DDI methods (ProbeHardware, Init, StartStreaming,
// StopStreaming, ICameraSensorControl, ICameraSensorInfo) are identical
// to the raw UDDF implementations described in the GMSL page.
};
The SerializerUbb and EepromUbb classes follow the same pattern – derive, provide the four required methods, then implement the DDI interface (IGmslSerializer or IEEPromAccess) as documented in GMSL.
Step 2 – Create the Module Driver#
Derive from ModuleUbb and implement doCreateUbbObjects(). This is the only pure virtual method you must provide:
class Cam123ModuleDriver : public gmslubb::ModuleUbb {
protected:
bool doCreateUbbObjects(GmslModuleContext::Config const& config) override {
return addSensorUbb(std::make_unique<Cam123Sensor>(config)) &&
addEepromUbb(std::make_unique<Cam123Eeprom>(config)) &&
addSerializerUbb(std::make_unique<Cam123Serializer>(config));
}
};
The order in which you call addSensorUbb(), addEepromUbb(), and addSerializerUbb() determines the initialization order. Deinit() reverses this order automatically.
Step 3 – Customize (Optional)#
Override ProbeHardware() when you need to perform pre-probe work such as GPIO setup on the serializer before sensor probing:
bool Cam123ModuleDriver::ProbeHardware(const GmslModuleContext& context,
bool alreadyInitialized) {
if (m_serializerUbb) {
GmslSerializerContext serCtx {};
serCtx.hwAccess = context.hwAccess;
serCtx.driverServices = context.driverServices;
m_serializerUbb->SetGPIOLevel(serCtx, FSYNC_GPIO, 0U);
m_serializerUbb->SetGPIOTxRxIDs(serCtx, gmslubb::GPIOTxRxConfig {
.serializerGpio = FSYNC_GPIO,
.destinationId = 0x1FU,
.direction = gmslubb::GPIOForwardingDirection::DeserToSer
});
}
return ModuleUbb::ProbeHardware(context, alreadyInitialized);
}
Override doPreInit() or doPostInit() for cross-component coordination that must happen before or after all components initialize:
bool Cam123ModuleDriver::doPreInit(const GmslModuleContext& context) {
if (!m_eepromUbbObjects.empty()) {
return m_eepromUbbObjects[0]->PrintNvidiaCameraPartName(context);
}
return true;
}
Step 4 – Discovery#
The discovery entrypoint does not change when you use UBB. Your ModuleUbb subclass is an IDriver, so you return it from uddf_discover_drivers() exactly as described in Discovery and Enumeration.
SerializerUbb Details#
SerializerUbb declares ProbeHardware() and Init() as override final and returns true unconditionally. The module driver does not drive the serializer lifecycle; the serdes initialization sequence drives it through the IGmslSerializer interface. Refer to Serdes Initialization Sequence in the GMSL page for the complete ordering.
Extending UBB#
The built-in SensorUbb, SerializerUbb, and EepromUbb classes cover the most common camera module components, but UBB is not limited to those three. You can create your own component type by deriving from DeviceUbb and registering it with addUbb().
class Cam123Illuminator : public gmslubb::DeviceUbb {
public:
explicit Cam123Illuminator(GmslModuleContext::Config const& config)
: DeviceUbb(config) {}
const char* GetName() const override { return "CAM123-illuminator"; }
bool Configure(GmslModuleContext::Config const& config) override {
return true;
}
uddf::ddi::DeviceTable GetDeviceTable() const override {
return {{ .i2cAddress = 0x60, .offsetWidth = 1, .dataWidth = 1 }};
}
uddf::ddi::GpioPinTable GetGpioPinTable() const override { return {}; }
bool ProbeHardware(GmslModuleContext const& ctx, bool alreadyInit) override {
return true;
}
bool Init(GmslModuleContext const& ctx) override {
return true;
}
};
Register your custom component in doCreateUbbObjects() using addUbb() with the appropriate ModuleComponentType:
bool doCreateUbbObjects(GmslModuleContext::Config const& config) override {
return addSensorUbb(std::make_unique<Cam123Sensor>(config)) &&
addSerializerUbb(std::make_unique<Cam123Serializer>(config)) &&
addUbb(std::make_unique<Cam123Illuminator>(config),
MODULE_COMPONENT_ILLUMINATOR);
}
Custom components participate in the same lifecycle automation as the built-in types: UBB calls Configure(), ProbeHardware(), Init(), and Deinit() on them, and aggregates their device and GPIO tables into the module-level tables.