Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions ds/audioOutputPort.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,52 @@ dsError_t AudioOutputPort::reInitializeAudioOutputPort()
return ret;
}

/**
* @fn AudioOutputPort::refreshHandle()
* @brief Re-fetches the server-side handle from the running dsmgr via
* dsGetAudioPort() IARM RPC and refreshes all cached port fields.
*
* Unlike reInitializeAudioOutputPort() which has a guard
* "if (_handle == -1)", this method runs UNCONDITIONALLY.
* It is safe to call after dsmgr crashes and restarts when _handle
* holds a stale non-zero value from the previous dsmgr process.
*
* Typical caller sequence after dsmgr restart:
* device::AudioOutputPort &p = Host::getInstance().getAudioOutputPort("HDMI0");
* dsError_t ret = p.refreshHandle(); // clears stale handle
* if (ret == dsERR_NONE) { p.isMuted(); ... } // now safe to use
*
* @return dsERR_NONE on success, dsError_t error code on failure.
*/
dsError_t AudioOutputPort::refreshHandle()
{
intptr_t old_handle = _handle;
dsError_t ret = dsGetAudioPort((dsAudioPortType_t)_type, _index, &_handle);

printf("\n[refreshHandle] AudioOutputPort type:%d index:%d "
"old_handle:0x%lX new_handle:0x%lX ret:%d\n",
_type, _index, (long)old_handle, (long)_handle, ret);

Comment on lines +227 to +233
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AudioOutputPort::refreshHandle() introduces unconditional printf()s to stdout and prints intptr_t handles via %lX casts. With the new automatic DSMgr-restart refresh, this can generate noisy output in normal operation and the formatting isn’t portable. Use INT_INFO/INT_ERROR (or similar project logging) and PRIxPTR/uintptr_t formatting for handle values, or gate this behind a debug switch.

Copilot uses AI. Check for mistakes.
if (dsERR_NONE == ret) {
/* Refresh all cached fields that depend on _handle */
dsGetAudioEncoding (_handle, (dsAudioEncoding_t *)&_encoding);
dsGetStereoMode (_handle, (dsAudioStereoMode_t *)&_stereoMode, false);
dsGetAudioGain (_handle, &_gain);
dsGetAudioLevel (_handle, &_level);
dsGetAudioOptimalLevel(_handle, &_optimalLevel);
dsGetAudioMaxDB (_handle, &_maxDb);
dsGetAudioMinDB (_handle, &_minDb);
dsGetAudioDB (_handle, &_db);
dsIsAudioLoopThru (_handle, &_loopThru);
dsIsAudioMute (_handle, &_muted);
dsGetAudioDelay (_handle, &_audioDelayMs);
printf("[refreshHandle] AudioOutputPort cached fields refreshed OK.\n");
} else {
printf("[refreshHandle] AudioOutputPort FAILED – dsmgr not running? (ret=%d)\n", ret);
}
return ret;
}

/**
* @fn const AudioOutputPortType & AudioOutputPort::getType() const
* @brief This API is used to get the type of the audio output port. The type of audio output port represent the general capabilities of the port.
Expand Down
48 changes: 48 additions & 0 deletions ds/audioOutputPortConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "dsError.h"
#include "dsUtl.h"
#include "stdlib.h"
#include <unistd.h>
#include "dslogger.h"
#include <dlfcn.h>
#include "manager.hpp"
Expand Down Expand Up @@ -276,6 +277,53 @@ void AudioOutputPortConfig::release()
}

}

/**
* @fn AudioOutputPortConfig::refreshAllHandles()
* @brief Re-fetches the server-side _handle for EVERY audio port stored in
* _aPorts[] by calling AudioOutputPort::refreshHandle() on each entry.
*
* This mirrors the port construction loop in load():
* for (int i = 0; i < portSize; i++)
* _aPorts.push_back(AudioOutputPort(type, index, i));
* but instead of creating new objects it refreshes the handles of the
* existing ones in place – no DeInitialize/Initialize needed.
*
* Ports refreshed (example vendor config):
* SPDIF0 (dsAUDIOPORT_TYPE_SPDIF, index 0)
* SPEAKER0 (dsAUDIOPORT_TYPE_SPEAKER, index 0)
* HDMI_ARC0 (dsAUDIOPORT_TYPE_HDMI_ARC, index 0)
* HDMI0 (dsAUDIOPORT_TYPE_HDMI, index 0) -- if present
* The exact set depends on kAudioPorts[] in the vendor SOC config.
*
* @return dsERR_NONE if ALL ports refreshed successfully.
* First failure code encountered if any port fails (refresh of
* remaining ports continues regardless so all are attempted).
*/
dsError_t AudioOutputPortConfig::refreshAllHandles()
{
dsError_t result = dsERR_NONE;

INT_INFO("[refreshAllHandles] Refreshing handles for %zu audio port(s)",
_aPorts.size());

for (size_t i = 0; i < _aPorts.size(); i++) {
dsError_t ret = _aPorts.at(i).refreshHandle();
if (ret == dsERR_NONE) {
INT_INFO("[refreshAllHandles] Port[%zu] %s OK",
i, _aPorts.at(i).getName().c_str());
} else {
INT_ERROR("[refreshAllHandles] Port[%zu] %s FAILED (ret=%d)",
i, _aPorts.at(i).getName().c_str(), ret);
if (result == dsERR_NONE)
result = ret; /* capture first failure, keep going */
}
}

INT_INFO("[refreshAllHandles] Done. result=%d (%s)",
result, result == dsERR_NONE ? "ALL OK" : "PARTIAL FAIL");
return result;
}
}
/** @} */
/** @} */
10 changes: 8 additions & 2 deletions ds/audioOutputPortConfig.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,16 @@ class AudioOutputPortConfig {

void load(audioConfigs_t* dynamicAudioConfigs);
void release();
dsError_t refreshAllHandles(); /*!< Re-fetch _handle for every port in _aPorts[].
* Mirrors the load() port loop but calls
* AudioOutputPort::refreshHandle() on each
* existing object instead of constructing new ones.
* Call after dsmgr crash+restart to clear stale
* handles for ALL audio ports in one shot. */

Comment on lines 77 to 85
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AudioOutputPortConfig::refreshAllHandles() is declared to return dsError_t, but this header doesn’t include dsError.h (or otherwise guarantee dsError_t is visible). This can break compilation for translation units that include audioOutputPortConfig.hpp without including dsError.h first (e.g. ds/manager.cpp). Add the proper include (or change the return type to something already declared here).

Copilot uses AI. Check for mistakes.
};
}; /* class AudioOutputPortConfig */

}
} /* namespace device */

#endif /* _DS_AUDIOOUTPUTPORTCONFIG_HPP_ */

Expand Down
3 changes: 3 additions & 0 deletions ds/include/audioOutputPort.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ class AudioOutputPort : public Enumerable {

dsError_t setEnablePort(bool enabled);
dsError_t reInitializeAudioOutputPort();
dsError_t refreshHandle(); /*!< Re-fetch _handle from running dsmgr after crash+restart.
* Unlike reInitializeAudioOutputPort(), this runs
* unconditionally even when _handle is stale (non-zero). */
void enable();
void disable();

Expand Down
1 change: 1 addition & 0 deletions ds/include/hdmiIn.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@

#include <stdint.h>
#include <vector>
#include <string>

#include "dsTypes.h"
#include "dsError.h"
Expand Down
1 change: 1 addition & 0 deletions ds/include/videoDevice.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class VideoDevice : public DSConstant {
int setFRFMode(int frfmode) const;
int getCurrentDisframerate(char *framerate) const;
int setDisplayframerate(const char *framerate) const;
int refreshHandle();
};

}
Expand Down
4 changes: 4 additions & 0 deletions ds/include/videoOutputPort.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,10 @@ class VideoOutputPort : public Enumerable {
unsigned int getPreferredColorDepth(bool persist = true) ;
void setPreferredColorDepth(const unsigned int colordepth, bool persist = true);
void getColorDepthCapabilities (unsigned int *capabilities) const;
int refreshHandle(); /*!< Re-fetch _handle (and inner Display _handle) from running dsmgr
* after crash+restart. Unconditional — works even when the
* current handle is stale (non-zero). Also refreshes
* _enabled and _displayConnected cached fields. */

private:
Display _display;
Expand Down
185 changes: 184 additions & 1 deletion ds/manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
#include <dlfcn.h>
#include "dsHALConfig.h"
#include "frontPanelConfig.hpp"
#include "dsMgr.h" /* IARM_BUS_DSMGR_NAME, IARM_BUS_DSMGR_EVENT_RESTARTED */
#include "libIBus.h" /* IARM_Bus_RegisterEventHandler / UnRegisterEventHandler */
#include <thread> /* std::thread for deferred handle refresh */
#include <chrono> /* std::chrono::milliseconds */

/**
* @file manager.cpp
Expand All @@ -68,6 +72,157 @@ int Manager::IsInitialized = 0; //!< Indicates the application has initialized
static std::mutex gManagerInitMutex;
static dsError_t initializeFunctionWithRetry(const char* functionName, std::function<dsError_t()> initFunc);

/**
* @brief IARM_BUS_DSMGR_EVENT_RESTARTED handler registered by Manager::Initialize().
*
* Fired by dsmgr after it has fully re-initialised its HAL — i.e. after
* dsAudioPortInit / dsVideoPortInit / dsVideoDeviceInit have all completed
* in the new dsmgr process.
*
* All three libds config singletons hold stale intptr_t handles that point
* into the address space of the *previous* dsmgr process. This handler
* refreshes them unconditionally so that subsequent API calls use handles
* valid in the new dsmgr process.
*
* Registered in Manager::Initialize() and unregistered in
* Manager::DeInitialize() so its lifetime exactly matches the Manager's.
*/
static void dsMgrRestartedHandler(const char* owner, IARM_EventId_t eventId,
void* /*data*/, size_t /*len*/)
{
INT_INFO("[Manager] IARM_BUS_DSMGR_EVENT_RESTARTED received owner=%s eventId=%d",
owner, eventId);

if (std::string(IARM_BUS_DSMGR_NAME) != std::string(owner)) {
INT_ERROR("unexpected owner '%s', ignoring", owner);
return;
}

std::lock_guard<std::mutex> lock(gManagerInitMutex);
if (Manager::IsInitialized == 0) {
/* Manager has been torn down; ignore the event */
INT_WARN("Manager not initialized, skipping refresh");
return;
}

INT_INFO("Refreshing ALL libds handles (deferred — spawning retry thread)");

/*
* IMPORTANT: Do NOT call refreshAllHandles() synchronously here.
*
* This handler is dispatched while dsmgr is still inside
* IARM_Bus_BroadcastEvent(). Any IARM_Bus_Call() back into DSMgr
* from this context hits the RPC dispatcher while it is busy and
* returns IARM_RESULT_IPCCORE_FAIL — leaving every handle stale.
*
* Fix: spawn a detached thread that sleeps before calling back into
* dsmgr (letting BroadcastEvent return) and retries up to 3 times
* with escalating delays covering ~3× the observed 600 ms restart time.
*/
std::thread([]() {
/* Delays tuned to the ~600 ms restart time observed on device.
* RESTARTED fires at end of DSMgr_Start() so dsmgr is already
* RPC-ready; only BroadcastEvent dispatch (~2 ms) needs to clear.
* 200 ms → fast path | 600 ms → full restart | 1200 ms → 2×
* Total window: 2.0 s. Tune DELAYS_MS[] to device RestartSec= if needed. */
static const int DELAYS_MS[] = { 200, 600, 1200 };
static const int MAX_ATTEMPTS = static_cast<int>(
sizeof(DELAYS_MS) / sizeof(DELAYS_MS[0]));

int cumulativeWaitMs = 0;
for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
const int delayMs = DELAYS_MS[attempt - 1];
cumulativeWaitMs += delayMs;

/* Sleep so BroadcastEvent has returned before we call back. */
std::this_thread::sleep_for(std::chrono::milliseconds(delayMs));

{
std::lock_guard<std::mutex> lk(gManagerInitMutex);
if (Manager::IsInitialized == 0) {
INT_WARN("[refreshThread] Manager deinitialized, aborting");
return;
}
}

INT_INFO("[refreshThread] attempt %d/%d (after %d ms delay)",
attempt, MAX_ATTEMPTS, delayMs);

dsError_t audioRet = AudioOutputPortConfig::getInstance().refreshAllHandles();
dsError_t videoPortRet = static_cast<dsError_t>(
VideoOutputPortConfig::getInstance().refreshAllHandles());
dsError_t videoDevRet = static_cast<dsError_t>(
VideoDeviceConfig::getInstance().refreshAllHandles());

INT_INFO("[refreshThread] attempt %d audioRet=%d videoPortRet=%d videoDevRet=%d",
attempt, audioRet, videoPortRet, videoDevRet);

if (audioRet == dsERR_NONE && videoPortRet == dsERR_NONE && videoDevRet == dsERR_NONE) {
INT_INFO("[refreshThread] ALL handles refreshed OK on attempt %d "
"(total wait %d ms)", attempt, cumulativeWaitMs);
return;
}

if (attempt < MAX_ATTEMPTS) {
INT_WARN("[refreshThread] attempt %d failed (a=%d vp=%d vd=%d), "
"retrying in %d ms",
attempt, audioRet, videoPortRet, videoDevRet,
DELAYS_MS[attempt]); /* next delay */
}
}

/* Last-resort: all refreshAllHandles() attempts failed after ~2 s.
* Do a full DeInitialize/Initialize to rebuild all libds singletons
* and HAL bindings from scratch against the running dsmgr.
* IsInitialized is a ref-count — force it to 1 so one DeInitialize()
* reaches 0 (triggers real teardown) and one Initialize() reaches 1
* (triggers real re-init); restore original count afterwards. */
INT_ERROR("[refreshThread] FAILED to refresh handles after %d attempts "
"(~2 s window exhausted) — initiating full DeInitialize/Initialize",
MAX_ATTEMPTS);

int savedRefCount = 0;
{
std::lock_guard<std::mutex> lk(gManagerInitMutex);
savedRefCount = Manager::IsInitialized;
}

if (savedRefCount <= 0) {
INT_WARN("[refreshThread] IsInitialized=%d — skipping force re-init",
savedRefCount);
return;
}

INT_INFO("[refreshThread] force re-init: savedRefCount=%d", savedRefCount);

{
std::lock_guard<std::mutex> lk(gManagerInitMutex);
Manager::IsInitialized = 1; /* arm: next DeInitialize() will reach 0 */
}

INT_INFO("[refreshThread] calling DeInitialize (will trigger real teardown)");
Manager::DeInitialize(); /* count 1 → 0 → dsAudioPortTerm + release() */

INT_INFO("[refreshThread] calling Initialize (will trigger real HAL re-init)");
try {
Manager::Initialize(); /* count 0 → 1 → dsAudioPortInit + load() + handler re-reg */
}
catch (const std::exception& ex) {
INT_ERROR("[refreshThread] Initialize threw: %s — aborting", ex.what());
return;
}

/* Restore count so callers still have a matching DeInitialize(). */
{
std::lock_guard<std::mutex> lk(gManagerInitMutex);
Manager::IsInitialized = savedRefCount;
}

INT_INFO("[refreshThread] force re-init complete — IsInitialized restored to %d",
savedRefCount);
}).detach();
}

bool LoadDLSymbols(void* pDLHandle, const dlSymbolLookup* symbols, int numberOfSymbols)
{
int currentSymbols = 0;
Expand Down Expand Up @@ -297,6 +452,22 @@ void Manager::Initialize()
device::DEVICE_CAPABILITY_AUDIO_PORT |
device::DEVICE_CAPABILITY_VIDEO_DEVICE |
device::DEVICE_CAPABILITY_FRONT_PANEL);

/* Register the dsmgr-restart handler so that libds handles are
* refreshed automatically whenever dsmgr crashes and restarts.
* Registered here (after all HAL inits) so handles are valid
* before the first event can arrive. Unregistered in
* DeInitialize() to match the Manager lifetime exactly. */
IARM_Result_t iarmRet = IARM_Bus_RegisterEventHandler(
IARM_BUS_DSMGR_NAME,
IARM_BUS_DSMGR_EVENT_RESTARTED,
dsMgrRestartedHandler);
if (IARM_RESULT_SUCCESS != iarmRet) {
INT_ERROR("[Manager] Failed to register IARM_BUS_DSMGR_EVENT_RESTARTED handler (ret=%d)",
iarmRet);
} else {
INT_INFO("[Manager] IARM_BUS_DSMGR_EVENT_RESTARTED handler registered OK");
}
Comment on lines +456 to +470
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change adds new, high-risk behavior (auto handle refresh + retry thread + last-resort DeInitialize/Initialize) but there’s no corresponding unit/integration coverage. Since the repo already has Boost-based tests for Audio/Video config and RPC clients, please add tests that exercise refreshAllHandles()/refreshHandle() (and ideally the restart-handler path) using the existing IARM stubs, so regressions in restart recovery are caught automatically.

Copilot uses AI. Check for mistakes.
}
}
catch(const Exception &e) {
Expand Down Expand Up @@ -343,7 +514,19 @@ void Manager::DeInitialize()
INT_INFO("Entering ... count %d with thread id %lu",IsInitialized,pthread_self());
if(IsInitialized>0)IsInitialized--;
if (0 == IsInitialized) {


/* Unregister the dsmgr-restart handler before tearing down HAL
* so no refresh can race with the release() / Term() calls below. */
IARM_Result_t iarmRet = IARM_Bus_UnRegisterEventHandler(
IARM_BUS_DSMGR_NAME,
IARM_BUS_DSMGR_EVENT_RESTARTED);
if (IARM_RESULT_SUCCESS != iarmRet) {
INT_ERROR("[Manager] Failed to unregister IARM_BUS_DSMGR_EVENT_RESTARTED handler (ret=%d)",
iarmRet);
} else {
INT_INFO("[Manager] IARM_BUS_DSMGR_EVENT_RESTARTED handler unregistered OK");
}

VideoDeviceConfig::getInstance().release();
VideoOutputPortConfig::getInstance().release();
AudioOutputPortConfig::getInstance().release();
Expand Down
Loading
Loading