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

uddf/ddi/interfaces/IGmslDeserializer.hpp

DeserializerContext

Camera module

uddf/ddi/interfaces/IGmslModuleControl.hpp

GmslModuleContext

Serializer (sub-component)

uddf/ddi/interfaces/IGmslSerializer.hpp

GmslSerializerContext

Deserializer power

uddf/ddi/interfaces/IGmslDeserializerPowerControl.hpp

GmslDeserializerPowerControlContext

Module power

uddf/ddi/interfaces/IGmslModulePowerControl.hpp

GmslModulePowerControlContext

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

ConfigureDriver(context, i2cDevices, gpioPins)

First call. Register all I2C devices and GPIO pins in the output tables. Hardware access is not permitted (context.hwAccess is nullptr). Validate the configuration in context.basicConfig and return false to reject unsupported configurations.

ProbeHardware(context, alreadyInitialized)

Verify the deserializer is present and is the expected hardware revision. When alreadyInitialized is true, CBA has already set up the hardware; check state without re-initializing. Init() will not be called in this case.

Init(context)

Perform full hardware initialization after a successful probe.

ControlLink(context, linkIndex, enableLink)

Enable or disable a single GMSL link. Called before SerInit() to isolate one link.

InitWithSerializer(context, linkIndex)

Called after IGmslSerializer::SerInit() completes on linkIndex. The serializer is available at context.initConfig.serializers[linkIndex]. Use this for GPIO forwarding setup and initial diagnostic tests.

FinalizeInitWithSerializer(context, linkIndex)

Called after all module elements on linkIndex are initialized. Other links may now be re-enabled.

CheckLinkLock(context, linkMask)

Return true if all links set in linkMask (bit 0 = link 0) have achieved lock.

GetErrorSize(context, maxErrorSize)

Populate maxErrorSize with the maximum buffer size that GetErrorInfo() may write. Required for safety-critical pre-allocated error buffers.

GetErrorInfo(context, buffer, bufferSize, actualSize, isRemoteError, linkErrorMask)

Fill buffer with diagnostic error data. Set isRemoteError if a remote serializer fault is detected. Set bits in linkErrorMask for each link that has an error.

StartStreaming(context, linkMask)

Start video streaming on the links in linkMask.

StopStreaming(context, linkMask)

Stop video streaming on the links in linkMask.

Deinit(context)

Shut down the deserializer hardware.

Reset(context)

Reset to a known-good state equivalent to post-Init(). Called to recover from a failure that occurred after Init() succeeded.

DeserializerContext Fields#

The following fields are available at every entrypoint after ConfigureDriver():

context.basicConfig

Field

Description

name

Deserializer driver name (for example, "Max96724GmslDeserializer").

i2cAddress

I2C address of the deserializer chip.

i2cPortNum

SoC I2C bus number.

desI2CPort

Deserializer-side I2C port number.

pwrPort

Power port number.

txPortNum

TX port number (UINT32_MAX if unused).

csiPort

CSI port enum (CSI_PORT_A, CSI_PORT_AB, etc.).

phyMode

PHY mode: PhyMode::DPHY or PhyMode::CPHY.

dphyRate / cphyRate

PHY rates in kHz for each lane configuration index.

fsyncMode

Frame sync mode: FsyncMode::EXTERNAL or FsyncMode::OSC_MANUAL.

dataType

Image format: RAW8, RAW10, RAW12, RAW16, RGB, YUV_8, YUV_10.

replicationInfo

Up to two optional ReplicationEntry pairs (source PHY → destination PHY).

longCables (non-safety only)

Per-link long-cable flag array.

context.initConfig (available from ProbeHardware() onwards):

Field

Description

linkMask

Bitmask of active links.

linkModes

Per-link DeserializerLinkModeConfig (index and DeserializerLinkMode).

serializers

Array of ModuleComponent* pointers, one per link. Use interface_cast<IGmslSerializer> to access the serializer interface.

isEmbeddedDataTypeEnabled

Per-link flag indicating whether embedded data lines are active.

numSensorsPerLink

Number of sensors per link (for example, 2 for a HAWK dual-sensor module).

moduleConfigObjects

Per-link IInterfaceProvider* for deserializer-to-module configuration objects.

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

ConfigureDriver(context, i2cDevices, gpioPins)

First call. Register I2C devices and GPIO pins. context.hwAccess is nullptr. Validate context.config and return false to reject.

AfterPowerOn(context)

Called after the module power rail is enabled. Restore hardware state or perform post-power checks. Called before ProbeHardware().

ProbeHardware(context, alreadyInitialized)

Verify hardware identity. When alreadyInitialized is true, verify state without reinitializing (Init() will not be called).

Init(context)

Initialize the module hardware (typically delegates to serializer, sensor, and EEPROM sub-components in order).

StartStreaming(context)

Start sensor streaming.

StopStreaming(context)

Stop sensor streaming.

Authenticate(context)

Perform hardware authentication. Return true if authentication is not required.

BeforePowerOff(context)

Prepare hardware for power removal.

Deinit(context)

Shut down module hardware.

Reset(context)

Reset to a known-good post-Init() state.

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. Return 0x1F when the module does not require a specific route.

  • GmslSerializerContext::GmslSerializerConfig::useExternalFsync tells the serializer driver to configure GPIO routing for external FSYNC.

  • DeserializerContext::InitConfig::fsyncTxIdPerLink carries the per-link FSYNC TX ID to the deserializer driver during initialization.

GmslModuleContext Fields#

context.config

Field

Description

linkIndex

Zero-based link index this module is attached to on the deserializer.

csiPort

CSI port assigned to this module.

fsyncMode

Frame sync mode: EXTERNAL or OSC_MANUAL.

sensorInfoList

List of SensorInfo structs, one per sensor. Each provides i2cAddress, resolution, pixelFormat, frameRate, isAuthEnabled, and embedded line counts.

deserConfigObject

Optional IInterfaceProvider* from the deserializer driver. May be nullptr if the deserializer does not provide a config object, or during ConfigureDriver().

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

MODULE_COMPONENT_SENSOR

Camera sensor (ICameraSensorControl, ICameraSensorInfo)

MODULE_COMPONENT_SERIALIZER

GMSL serializer (IGmslSerializer)

MODULE_COMPONENT_EEPROM

EEPROM (IEEPromAccess)

MODULE_COMPONENT_ILLUMINATOR

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

SerGetInfo(info)

Populate info.model with the serializer model string.

SerInit(context)

Initialize the serializer while the link is isolated. Set up I2C address translations from context.config.addressTranslations. This is step 2 of the serdes initialization sequence.

SerPrepareForModuleInit(context)

Complete any remaining serializer setup before the rest of the module is initialized. This is step 4 of the serdes initialization sequence.

SerFinalizeInit(context)

Complete serializer initialization after all module elements are ready. This is step 7 of the serdes initialization sequence.

SerEnableErrorPin(context, enable)

Enable or disable the ERRB error reporting pin.

SerConfigureGPIOForwarding(context, gpioForwarding)

Configure GPIO forwarding between the deserializer and the camera module.

GmslSerializerContext Fields#

Field

Description

hwAccess

Hardware access interface.

driverServices

Driver services interface.

config.name

Serializer name string.

config.addressTranslations

Vector of AddressTranslation structs, each mapping a physicalAddress to a virtualAddress. Apply these in SerInit() by programming the serializer’s address translation table.

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

busNumber

I2C bus number the deserializer is on. Valid range: 0–15 for hardware buses, 255 for simulator.

gpioPinIndex

std::optional<uint32_t>. When present, a GPIO pin controls the deserializer power supply (direct GPIO backend). When absent, an I/O expander is used.

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

EnableModulePower(context, index)

Enable power for the module at zero-based index.

DisableModulePower(context, index)

Disable power for the module at zero-based index.

EnableModulePowerMask(context, mask)

Enable power for all modules whose bit is set in mask (bit 0 = module 0).

DisableModulePowerMask(context, mask)

Disable power for all modules whose bit is set in mask.

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

doCreateUbbObjects() (required)

Create and register component objects. Called during ConfigureDriver().

ProbeHardware() override

Pre-probe work such as serializer GPIO setup.

doPreInit() override

Cross-component logic before individual component Init() calls.

doPostInit() override

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 IDriver and implement GetInterface(), returning the correct interface pointer for each UUID your driver handles and nullptr for all others.

  • Implement all pure virtual methods of IGmslDeserializer or IGmslModuleControl. Methods that are not applicable to your hardware should return true as 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=True in PyHSL and I2CAddressMode::Physical in raw reads for any device that must be addressed before virtual translations are configured.

  • Apply all AddressTranslation entries from context.config.addressTranslations in SerInit().

  • Use hslI2cAddress in 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.hwAccess from ConfigureDriver(); it will be nullptr.

Discovery#

  • Export uddf_discover_drivers() with C linkage.

  • Set DriverInfo.name to a string that exactly matches the deserInfo.name, serInfo.name, or powerControlInfo.deserializerInfo.name / moduleInfo.name fields in your JSON configuration files (case-sensitive).

  • Install the compiled shared library to /usr/lib/nvsipl_drv.