Guide to Writing GMSL UDDF Drivers#
This guide explains how to implement UDDF drivers for GMSL camera systems. It covers all
five driver types and uses runnable sample code drawn directly from
sipl/uddf/samples/drivers/gmsl/ and
sipl/uddf/samples/drivers/gmslpower/.
Before reading this guide, you should be familiar with the UDDF driver model described in Guide to Writing UDDF Drivers and the GMSL topology described in GMSL Architecture Overview.
Overview of Driver Types#
Driver Type |
Interface Header |
Context Struct |
|---|---|---|
Deserializer |
|
|
Camera module |
|
|
Serializer (sub-component) |
|
|
Deserializer power |
|
|
Module power |
|
|
All context structs provide two CDI handles, available at every entrypoint except
ConfigureDriver():
hwAccess(uddf::cdi::IHardwareAccess*) – hardware communication (I2C, GPIO, HSL).driverServices(uddf::cdi::IDriverServices*) – logging.
For ConfigureDriver(), hwAccess is nullptr to prevent hardware access before
the device table is established.
Deserializer Driver (IGmslDeserializer)#
A deserializer driver controls a GMSL deserializer chip such as the MAX96712 or MAX96724.
Your class inherits from both uddf::ddi::IDriver and
uddf::ddi::interfaces::IGmslDeserializer.
Class Declaration#
#include "uddf/ddi/IDriver.hpp"
#include "uddf/ddi/interfaces/IGmslDeserializer.hpp"
using namespace uddf::ddi::interfaces;
class MyDeserDriver final : public uddf::ddi::IDriver,
public IGmslDeserializer
{
public:
uddf::ddi::IInterface* GetInterface(
const uddf::ddi::UUID& uuid) noexcept override
{
if (uuid == IGmslDeserializer::id) {
return static_cast<IGmslDeserializer*>(this);
}
return nullptr;
}
bool ConfigureDriver(DeserializerContext& context,
uddf::ddi::DeviceTable& i2cDevices,
uddf::ddi::GpioPinTable& gpioPins) override;
bool ProbeHardware(DeserializerContext& context,
bool alreadyInitialized) override;
bool Init(DeserializerContext& context) override;
bool InitWithSerializer(DeserializerContext& context,
uint8_t linkIndex) override;
bool FinalizeInitWithSerializer(DeserializerContext& context,
uint8_t linkIndex) override;
bool ControlLink(DeserializerContext& context,
uint8_t linkIndex, bool enableLink) override;
bool CheckLinkLock(DeserializerContext& context,
uint8_t linkMask) override;
bool GetErrorSize(DeserializerContext& context,
size_t& maxErrorSize) override;
bool GetErrorInfo(DeserializerContext& context,
uint8_t* buffer, size_t bufferSize,
size_t& actualSize, bool& isRemoteError,
uint8_t& linkErrorMask) override;
bool StartStreaming(DeserializerContext& context,
uint8_t linkMask) override;
bool StopStreaming(DeserializerContext& context,
uint8_t linkMask) override;
bool Deinit(DeserializerContext& context) override;
bool Reset(DeserializerContext& context) override;
};
Entrypoint Reference#
Method |
Description |
|---|---|
|
First call. Register all I2C devices and GPIO pins in the output tables. Hardware
access is not permitted ( |
|
Verify the deserializer is present and is the expected hardware revision. When
|
|
Perform full hardware initialization after a successful probe. |
|
Enable or disable a single GMSL link. Called before |
|
Called after |
|
Called after all module elements on |
|
Return |
|
Populate |
|
Fill |
|
Start video streaming on the links in |
|
Stop video streaming on the links in |
|
Shut down the deserializer hardware. |
|
Reset to a known-good state equivalent to post- |
DeserializerContext Fields#
The following fields are available at every entrypoint after ConfigureDriver():
context.basicConfig
Field |
Description |
|---|---|
|
Deserializer driver name (for example, |
|
I2C address of the deserializer chip. |
|
SoC I2C bus number. |
|
Deserializer-side I2C port number. |
|
Power port number. |
|
TX port number ( |
|
CSI port enum ( |
|
PHY mode: |
|
PHY rates in kHz for each lane configuration index. |
|
Frame sync mode: |
|
Image format: |
|
Up to two optional |
|
Per-link long-cable flag array. |
context.initConfig (available from ProbeHardware() onwards):
Field |
Description |
|---|---|
|
Bitmask of active links. |
|
Per-link |
|
Array of |
|
Per-link flag indicating whether embedded data lines are active. |
|
Number of sensors per link (for example, 2 for a HAWK dual-sensor module). |
|
Per-link |
Camera Module Driver (IGmslModuleControl)#
A camera module driver controls all hardware components on one camera module. It implements
both IGmslModuleControl (lifecycle control) and IModuleComponentAccess (component
enumeration).
Class Declaration#
#include "uddf/ddi/IDriver.hpp"
#include "uddf/ddi/interfaces/IGmslModuleControl.hpp"
#include "uddf/ddi/interfaces/IModuleComponentAccess.hpp"
using namespace uddf::ddi::interfaces;
class MyModuleDriver final : public uddf::ddi::IDriver,
public IGmslModuleControl,
public IModuleComponentAccess
{
public:
uddf::ddi::IInterface* GetInterface(
const uddf::ddi::UUID& uuid) noexcept override
{
if (uuid == IGmslModuleControl::id) {
return static_cast<IGmslModuleControl*>(this);
}
if (uuid == IModuleComponentAccess::id) {
return static_cast<IModuleComponentAccess*>(this);
}
return nullptr;
}
// IGmslModuleControl
bool ConfigureDriver(const GmslModuleContext& context,
uddf::ddi::DeviceTable& i2cDevices,
uddf::ddi::GpioPinTable& gpioPins) override;
bool ProbeHardware(const GmslModuleContext& context,
bool alreadyInitialized) override;
bool Init(const GmslModuleContext& context) override;
bool StartStreaming(const GmslModuleContext& context) override;
bool StopStreaming(const GmslModuleContext& context) override;
bool Deinit(const GmslModuleContext& context) override;
bool Reset(const GmslModuleContext& context) override;
bool Authenticate(const GmslModuleContext& context) override;
bool BeforePowerOff(const GmslModuleContext& context) override;
bool AfterPowerOn(const GmslModuleContext& context) override;
// IModuleComponentAccess
ModuleComponent* GetComponent(ModuleComponentType type,
size_t componentIndex) override;
size_t GetComponentCount(ModuleComponentType type) override;
};
Entrypoint Reference#
Method |
Description |
|---|---|
|
First call. Register I2C devices and GPIO pins. |
|
Called after the module power rail is enabled. Restore hardware state or perform
post-power checks. Called before |
|
Verify hardware identity. When |
|
Initialize the module hardware (typically delegates to serializer, sensor, and EEPROM sub-components in order). |
|
Start sensor streaming. |
|
Stop sensor streaming. |
|
Perform hardware authentication. Return |
|
Prepare hardware for power removal. |
|
Shut down module hardware. |
|
Reset to a known-good post- |
Stereo FSYNC Hooks#
JetPack 7.2 adds public hooks for GMSL stereo external-FSYNC routing:
IGmslModuleControl::GetModuleFsyncTxId()lets a module driver report the serializer RX ID used for FSYNC routing. Return0x1Fwhen the module does not require a specific route.GmslSerializerContext::GmslSerializerConfig::useExternalFsynctells the serializer driver to configure GPIO routing for external FSYNC.DeserializerContext::InitConfig::fsyncTxIdPerLinkcarries the per-link FSYNC TX ID to the deserializer driver during initialization.
GmslModuleContext Fields#
context.config
Field |
Description |
|---|---|
|
Zero-based link index this module is attached to on the deserializer. |
|
CSI port assigned to this module. |
|
Frame sync mode: |
|
List of |
|
Optional |
IModuleComponentAccess#
The camera HAL retrieves sub-components through IModuleComponentAccess:
auto* compAccess = interface_cast<IModuleComponentAccess>(moduleDriver);
// Retrieve the serializer
auto* ser = interface_cast<IGmslSerializer>(
compAccess->GetComponent(MODULE_COMPONENT_SERIALIZER, 0));
// Retrieve the first sensor
auto* sensor = interface_cast<ICameraSensorControl>(
compAccess->GetComponent(MODULE_COMPONENT_SENSOR, 0));
Standard component type constants are defined in uddf/ddi/ModuleComponentTypes.hpp:
Constant |
Component type |
|---|---|
|
Camera sensor ( |
|
GMSL serializer ( |
|
EEPROM ( |
|
IR illuminator or LED (custom interface) |
Serializer Sub-Component (IGmslSerializer)#
The serializer is not a top-level UDDF driver. It is a sub-component of the camera module
driver, returned by IModuleComponentAccess::GetComponent(MODULE_COMPONENT_SERIALIZER, 0).
The SIPL camera HAL calls the serializer methods directly through IGmslSerializer.
Interface Methods#
Method |
Description |
|---|---|
|
Populate |
|
Initialize the serializer while the link is isolated. Set up I2C address
translations from |
|
Complete any remaining serializer setup before the rest of the module is initialized. This is step 4 of the serdes initialization sequence. |
|
Complete serializer initialization after all module elements are ready. This is step 7 of the serdes initialization sequence. |
|
Enable or disable the ERRB error reporting pin. |
|
Configure GPIO forwarding between the deserializer and the camera module. |
GmslSerializerContext Fields#
Field |
Description |
|---|---|
|
Hardware access interface. |
|
Driver services interface. |
|
Serializer name string. |
|
Vector of |
I2C Address Translation#
GMSL serializers support I2C address translation so that multiple camera modules with
devices at the same physical address can be programmed independently via unique virtual
addresses. UDDF assigns virtual addresses automatically. Drivers must apply the translations
provided in context.config.addressTranslations during SerInit().
By default, all I2C operations go through virtual address translation. To bypass virtual
translation and use the physical address directly – necessary during serializer
initialization before the translation table is set up – use the
I2CAddressMode enum (from CDITypes.hpp):
Static HSL (PyHSL) – use the no_virtual_address flag:
# Address used before virtual translations are configured
phys_ser = I2CDevice(SER_PHYS_ADDR, 16, 8, 'ser_phys', no_virtual_address=True)
# Address used after virtual translations are configured
virt_ser = I2CDevice(SER_VIRT_ADDR, 16, 8, 'ser_virt')
Raw ReadI2C – pass I2CAddressMode::Physical:
context.hwAccess->ReadI2C(deviceIdx, offset, length, buf,
uddf::cdi::I2CAddressMode::Physical);
Dynamic HSL – select the I2C builder by mode:
auto& seq = context.hwAccess->GetDynamicSequence();
// Physical address (bypass translation)
auto* phys = seq.i2cBuilder(deviceIdx, uddf::cdi::I2CAddressMode::Physical);
// Virtual address (default, translation applied)
auto* virt = seq.i2cBuilder(deviceIdx);
Both ReadI2C and i2cBuilder default to I2CAddressMode::Virtual.
Runtime Address Remapping (hslI2cAddress)#
When a device’s runtime I2C address differs from the address compiled into a PyHSL sequence
(for example, a power loadswitch whose address varies across board revisions), use the
optional hslI2cAddress field in the driver’s device table:
i2cDevices.push_back({
.i2cAddress = 0x29, // Actual runtime address
.hslI2cAddress = 0x28, // Address used in PyHSL sequences
.offsetWidth = 1,
.dataWidth = 1,
});
When hslI2cAddress is present, the camera HAL rewrites PyHSL sequences to use
i2cAddress transparently.
Deserializer Power Driver (IGmslDeserializerPowerControl)#
This optional driver controls the power rail for the deserializer chip. It is declared in
uddf/ddi/interfaces/IGmslDeserializerPowerControl.hpp.
Class Declaration#
#include "uddf/ddi/IDriver.hpp"
#include "uddf/ddi/interfaces/IGmslDeserializerPowerControl.hpp"
using namespace uddf::ddi::interfaces;
class MyDeserPowerDriver final
: public uddf::ddi::IDriver,
public IGmslDeserializerPowerControl
{
public:
uddf::ddi::IInterface* GetInterface(
const uddf::ddi::UUID& uuid) noexcept override
{
if (uuid == IGmslDeserializerPowerControl::id) {
return static_cast<IGmslDeserializerPowerControl*>(this);
}
return nullptr;
}
bool ConfigureDriver(
const GmslDeserializerPowerControlContext& context,
uddf::ddi::DeviceTable& i2cDevices,
uddf::ddi::GpioPinTable& gpioPins) override;
bool ProbeHardware(
const GmslDeserializerPowerControlContext& context,
bool alreadyInitialized) override;
bool Init(const GmslDeserializerPowerControlContext& context) override;
bool Deinit(const GmslDeserializerPowerControlContext& context) override;
bool Reset(const GmslDeserializerPowerControlContext& context) override;
bool EnablePower(
const GmslDeserializerPowerControlContext& context) override;
bool DisablePower(
const GmslDeserializerPowerControlContext& context) override;
};
Context Fields#
context.config
Field |
Description |
|---|---|
|
I2C bus number the deserializer is on. Valid range: 0–15 for hardware buses, 255 for simulator. |
|
|
Note
On NVIDIA Orin, both an I/O expander and direct GPIO control are supported. On NVIDIA Thor, only direct GPIO control is available (no I/O expander).
Module Power Driver (IGmslModulePowerControl)#
This optional driver controls power for individual camera modules on a single deserializer.
It is declared in uddf/ddi/interfaces/IGmslModulePowerControl.hpp.
Class Declaration#
#include "uddf/ddi/IDriver.hpp"
#include "uddf/ddi/interfaces/IGmslModulePowerControl.hpp"
using namespace uddf::ddi::interfaces;
class MyModulePowerDriver final
: public uddf::ddi::IDriver,
public IGmslModulePowerControl
{
public:
uddf::ddi::IInterface* GetInterface(
const uddf::ddi::UUID& uuid) noexcept override
{
if (uuid == IGmslModulePowerControl::id) {
return static_cast<IGmslModulePowerControl*>(this);
}
return nullptr;
}
bool ConfigureDriver(
const GmslModulePowerControlContext& context,
uddf::ddi::DeviceTable& i2cDevices,
uddf::ddi::GpioPinTable& gpioPins) override;
bool ProbeHardware(
const GmslModulePowerControlContext& context,
bool alreadyInitialized) override;
bool Init(const GmslModulePowerControlContext& context) override;
bool Deinit(const GmslModulePowerControlContext& context) override;
bool Reset(const GmslModulePowerControlContext& context) override;
bool EnableModulePower(
const GmslModulePowerControlContext& context,
size_t index) override;
bool DisableModulePower(
const GmslModulePowerControlContext& context,
size_t index) override;
bool EnableModulePowerMask(
const GmslModulePowerControlContext& context,
size_t mask) override;
bool DisableModulePowerMask(
const GmslModulePowerControlContext& context,
size_t mask) override;
};
Power Control Methods#
Method |
Description |
|---|---|
|
Enable power for the module at zero-based |
|
Disable power for the module at zero-based |
|
Enable power for all modules whose bit is set in |
|
Disable power for all modules whose bit is set in |
Context Fields#
context.config.i2cAddress — the native I2C address of the module power control device
(for example, a MAX20087 load switch).
UDDF Building Blocks (UBB)#
UBB is an optional convenience layer that reduces boilerplate for standard GMSL module drivers. It handles interface routing, component registration, device table aggregation, and lifecycle ordering automatically. UBB does not change the UDDF DDI interfaces; it is purely a thin wrapper.
UBB header files are located under uddf/drivers/common/ and uddf/coSerDes/common/.
When to use UBB: Your module follows the standard—one serializer, one or more sensors, optional EEPROM—and you want to focus on hardware-specific logic rather than boilerplate.
When to use raw DDI: You need non-standard GetInterface() routing, multiple
serializers on one module, or a lifecycle ordering that differs from the built-in UBB order.
Writing a Module Driver with UBB#
Step 1 – Derive component classes from the UBB bases:
#include "common/SensorUbb.hpp"
#include "common/SerializerUbb.hpp"
#include "common/EepromUbb.hpp"
class MyCamSensor : public gmslubb::SensorUbb {
public:
explicit MyCamSensor(GmslModuleContext::Config const& config)
: SensorUbb(config) {}
const char* GetName() const override { return "MyCam-sensor"; }
bool Configure(GmslModuleContext::Config const& config) override {
return true; // validate config fields here
}
uddf::ddi::DeviceTable GetDeviceTable() const override {
return {{ .i2cAddress = 0x1A, .offsetWidth = 2, .dataWidth = 1 }};
}
uddf::ddi::GpioPinTable GetGpioPinTable() const override {
return {};
}
// Implement ICameraSensorControl, ICameraSensorInfo, etc.
};
Step 2 – Derive the module driver from ModuleUbb and implement doCreateUbbObjects():
#include "common/ModuleUbb.hpp"
class MyCamModuleDriver : public gmslubb::ModuleUbb {
protected:
bool doCreateUbbObjects(
GmslModuleContext::Config const& config) override
{
return addSensorUbb(std::make_unique<MyCamSensor>(config)) &&
addEepromUbb(std::make_unique<MyCamEeprom>(config)) &&
addSerializerUbb(std::make_unique<MyCamSerializer>(config));
}
};
Component initialization order matches the addXxxUbb() call order.
Deinit() runs in reverse order automatically.
Step 3 – Override hooks as needed:
// Override ProbeHardware to run serializer GPIO setup before sensor probing
bool MyCamModuleDriver::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);
}
return ModuleUbb::ProbeHardware(context, alreadyInitialized);
}
// Override doPreInit to read EEPROM part name before sensor init
bool MyCamModuleDriver::doPreInit(const GmslModuleContext& context)
{
if (!m_eepromUbbObjects.empty()) {
return m_eepromUbbObjects[0]->PrintNvidiaCameraPartName(context);
}
return true;
}
Available UBB lifecycle hooks:
Hook |
When to use |
|---|---|
|
Create and register component objects. Called during |
|
Pre-probe work such as serializer GPIO setup. |
|
Cross-component logic before individual component |
|
Cross-component logic after all components have initialized. |
SerializerUbb declares ProbeHardware() and Init() as override final
returning true. The serializer lifecycle is driven by the serdes initialization sequence
through IGmslSerializer, not by the module driver directly.
Extending UBB with Custom Component Types#
For components beyond sensor/serializer/EEPROM (such as illuminators or IMUs), derive from
DeviceUbb and register with addUbb():
class MyCamIlluminator : public gmslubb::DeviceUbb {
public:
explicit MyCamIlluminator(GmslModuleContext::Config const& config)
: DeviceUbb(config) {}
const char* GetName() const override { return "MyCam-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; }
};
// In doCreateUbbObjects():
bool doCreateUbbObjects(GmslModuleContext::Config const& config) override {
return addSensorUbb(std::make_unique<MyCamSensor>(config)) &&
addSerializerUbb(std::make_unique<MyCamSerializer>(config)) &&
addUbb(std::make_unique<MyCamIlluminator>(config),
MODULE_COMPONENT_ILLUMINATOR);
}
Driver Library Entry Point#
Package all your drivers into a shared library that exports uddf_discover_drivers().
A single library may expose any combination of deserializer, module, and power drivers.
#include "uddf/ddi/discovery.hpp"
#include "MyDeserDriver.hpp"
#include "MyModuleDriver.hpp"
#include "MyDeserPowerDriver.hpp"
#include "MyModulePowerDriver.hpp"
static const uddf::ddi::DriverInfo g_deserInfo = {
.name = "MyGmslDeserializer",
.description = "MAX96724 deserializer driver for MyCam platform",
.vendor = "MyCorp",
.revision = "1.0.0",
};
static const uddf::ddi::DriverInfo g_moduleInfo = {
.name = "MyCamModule",
.description = "Camera module driver for MyCam sensors",
.vendor = "MyCorp",
.revision = "1.0.0",
};
class MyCamEnumerator final : public uddf::ddi::IDriverEnumerator {
public:
std::string_view GetName() const override { return "MyCamProvider"; }
size_t GetDriverCount() const override { return 2; }
const uddf::ddi::DriverInfo* GetDriverInfo(size_t index) const override {
if (index == 0) return &g_deserInfo;
if (index == 1) return &g_moduleInfo;
return nullptr;
}
std::unique_ptr<uddf::ddi::IDriver> CreateDriver(size_t index) override {
if (index == 0) return std::make_unique<MyDeserDriver>();
if (index == 1) return std::make_unique<MyModuleDriver>();
return nullptr;
}
};
extern "C" {
uddf::ddi::DriverEnumeratorPtr uddf_discover_drivers() {
return std::make_unique<MyCamEnumerator>();
}
}
The DriverInfo.name field is the key used to match your driver to JSON configuration
entries. See Integrating GMSL UDDF Drivers with SIPL for the exact matching rules.
Checklist for GMSL Driver Development#
Interface Implementation#
Inherit from
IDriverand implementGetInterface(), returning the correct interface pointer for each UUID your driver handles andnullptrfor all others.Implement all pure virtual methods of
IGmslDeserializerorIGmslModuleControl. Methods that are not applicable to your hardware should returntrueas a no-op.For the module driver, also implement
IModuleComponentAccess.Expose the serializer sub-component via
GetComponent(MODULE_COMPONENT_SERIALIZER, 0).
Hardware Access and HSL#
Register all I2C devices and GPIO pins in
ConfigureDriver()before returning.Use
no_virtual_address=Truein PyHSL andI2CAddressMode::Physicalin raw reads for any device that must be addressed before virtual translations are configured.Apply all
AddressTranslationentries fromcontext.config.addressTranslationsinSerInit().Use
hslI2cAddressin the device table when the runtime I2C address differs from the PyHSL sequence address.Never throw C++ exceptions from any entrypoint.
Do not access
context.hwAccessfromConfigureDriver(); it will benullptr.
Discovery#
Export
uddf_discover_drivers()with C linkage.Set
DriverInfo.nameto a string that exactly matches thedeserInfo.name,serInfo.name, orpowerControlInfo.deserializerInfo.name/moduleInfo.namefields in your JSON configuration files (case-sensitive).Install the compiled shared library to
/usr/lib/nvsipl_drv.