blob: 2f5dff416bf1a345f95ddd9b76b17bb93ac7b3af [file] [log] [blame]
/*
* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
* Changes from Qualcomm Innovation Center, Inc. are provided under the following license:
* Copyright (c) 2022, 2024 Qualcomm Innovation Center, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#define LOG_TAG "PAL: StreamCommon"
#define RXDIR 0
#define TXDIR 1
#include "StreamCommon.h"
#include "Session.h"
#include "kvh2xml.h"
#include "SessionAlsaPcm.h"
#include "ResourceManager.h"
#include "Device.h"
#include <unistd.h>
StreamCommon::StreamCommon(const struct pal_stream_attributes *sattr, struct pal_device *dattr,
const uint32_t no_of_devices, const struct modifier_kv *modifiers,
const uint32_t no_of_modifiers, const std::shared_ptr<ResourceManager> rm)
{
mStreamMutex.lock();
uint32_t in_channels = 0, out_channels = 0;
uint32_t attribute_size = 0;
if (rm->cardState == CARD_STATUS_OFFLINE) {
PAL_ERR(LOG_TAG, "Error:Sound card offline, can not create stream");
usleep(SSR_RECOVERY);
mStreamMutex.unlock();
throw std::runtime_error("Sound card offline");
}
session = NULL;
mGainLevel = -1;
std::shared_ptr<Device> dev = nullptr;
mStreamAttr = (struct pal_stream_attributes *)nullptr;
inBufSize = BUF_SIZE_CAPTURE;
outBufSize = BUF_SIZE_PLAYBACK;
inBufCount = NO_OF_BUF;
outBufCount = NO_OF_BUF;
mDevices.clear();
mPalDevice.clear();
currentState = STREAM_IDLE;
//Modify cached values only at time of SSR down.
cachedState = STREAM_IDLE;
bool isDeviceConfigUpdated = false;
PAL_DBG(LOG_TAG, "Enter");
//TBD handle modifiers later
mNoOfModifiers = 0; //no_of_modifiers;
mModifiers = (struct modifier_kv *) (NULL);
std::ignore = modifiers;
std::ignore = no_of_modifiers;
if (!sattr) {
PAL_ERR(LOG_TAG,"Error:invalid arguments");
mStreamMutex.unlock();
throw std::runtime_error("invalid arguments");
}
attribute_size = sizeof(struct pal_stream_attributes);
mStreamAttr = (struct pal_stream_attributes *) calloc(1, attribute_size);
if (!mStreamAttr) {
PAL_ERR(LOG_TAG, "Error:malloc for stream attributes failed %s", strerror(errno));
mStreamMutex.unlock();
throw std::runtime_error("failed to malloc for stream attributes");
}
memcpy(mStreamAttr, sattr, sizeof(pal_stream_attributes));
if (mStreamAttr->in_media_config.ch_info.channels > PAL_MAX_CHANNELS_SUPPORTED) {
PAL_ERR(LOG_TAG,"Error:in_channels is invalid %d", in_channels);
mStreamAttr->in_media_config.ch_info.channels = PAL_MAX_CHANNELS_SUPPORTED;
}
if (mStreamAttr->out_media_config.ch_info.channels > PAL_MAX_CHANNELS_SUPPORTED) {
PAL_ERR(LOG_TAG,"Error:out_channels is invalid %d", out_channels);
mStreamAttr->out_media_config.ch_info.channels = PAL_MAX_CHANNELS_SUPPORTED;
}
PAL_VERBOSE(LOG_TAG, "Create new Session for stream type %d", sattr->type);
session = Session::makeSession(rm, sattr);
if (!session) {
PAL_ERR(LOG_TAG, "Error:session creation failed");
free(mStreamAttr);
mStreamMutex.unlock();
throw std::runtime_error("failed to create session object");
}
PAL_VERBOSE(LOG_TAG, "Create new Devices with no_of_devices - %d", no_of_devices);
for (int i = 0; i < no_of_devices; i++) {
//Check with RM if the configuration given can work or not
//for e.g., if incoming stream needs 24 bit device thats also
//being used by another stream, then the other stream should route
dev = Device::getInstance((struct pal_device *)&dattr[i] , rm);
if (!dev) {
PAL_ERR(LOG_TAG, "Error:Device creation failed");
free(mStreamAttr);
//TBD::free session too
mStreamMutex.unlock();
throw std::runtime_error("failed to create device object");
}
mPalDevice.push_back(dattr[i]);
mStreamMutex.unlock();
isDeviceConfigUpdated = rm->updateDeviceConfig(&dev, &dattr[i], sattr);
mStreamMutex.lock();
if (isDeviceConfigUpdated)
PAL_VERBOSE(LOG_TAG, "Device config updated");
/* Create only update device attributes first time so update here using set*/
/* this will have issues if same device is being currently used by different stream */
mDevices.push_back(dev);
}
mStreamMutex.unlock();
PAL_DBG(LOG_TAG, "Exit. state %d", currentState);
return;
}
StreamCommon::~StreamCommon()
{
PAL_DBG(LOG_TAG, "Enter");
cachedState = STREAM_IDLE;
if (mStreamAttr) {
free(mStreamAttr);
mStreamAttr = (struct pal_stream_attributes *)NULL;
}
/*switch back to proper config if there is a concurrency and device is still running*/
for (int32_t i=0; i < mDevices.size(); i++)
rm->restoreDevice(mDevices[i]);
mDevices.clear();
mPalDevice.clear();
delete session;
session = nullptr;
PAL_DBG(LOG_TAG, "Exit");
}
int32_t StreamCommon::open()
{
int32_t status = 0;
PAL_DBG(LOG_TAG, "Enter. session handle - %pK device count - %zu", session,
mDevices.size());
mStreamMutex.lock();
if (rm->cardState == CARD_STATUS_OFFLINE) {
PAL_ERR(LOG_TAG, "Error:Sound card offline, can not open stream");
usleep(SSR_RECOVERY);
status = -EIO;
goto exit;
}
if (currentState == STREAM_IDLE) {
status = session->open(this);
if (0 != status) {
PAL_ERR(LOG_TAG, "Error:session open failed with status %d", status);
goto exit;
}
PAL_VERBOSE(LOG_TAG, "session open successful");
for (int32_t i = 0; i < mDevices.size(); i++) {
status = mDevices[i]->open();
if (0 != status) {
PAL_ERR(LOG_TAG, "Error:device open failed with status %d", status);
goto exit;
}
}
currentState = STREAM_INIT;
PAL_DBG(LOG_TAG, "streamLL opened. state %d", currentState);
} else if (currentState == STREAM_INIT) {
PAL_INFO(LOG_TAG, "Stream is already opened, state %d", currentState);
status = 0;
goto exit;
} else {
PAL_ERR(LOG_TAG, "Error:Stream is not in correct state %d", currentState);
//TBD : which error code to return here.
status = -EINVAL;
goto exit;
}
exit:
mStreamMutex.unlock();
PAL_DBG(LOG_TAG, "Exit ret %d", status)
return status;
}
//TBD: move this to Stream, why duplicate code?
int32_t StreamCommon::close()
{
int32_t status = 0;
mStreamMutex.lock();
if (currentState == STREAM_IDLE) {
PAL_INFO(LOG_TAG, "Stream is already closed");
mStreamMutex.unlock();
return status;
}
PAL_DBG(LOG_TAG, "Enter. session handle - %pK device count - %zu stream_type - %d state %d",
session, mDevices.size(), mStreamAttr->type, currentState);
if (currentState == STREAM_STARTED || currentState == STREAM_PAUSED) {
mStreamMutex.unlock();
status = stop();
if (0 != status)
PAL_ERR(LOG_TAG, "Error:stream stop failed. status %d", status);
mStreamMutex.lock();
}
rm->lockGraph();
status = session->close(this);
rm->unlockGraph();
if (0 != status) {
PAL_ERR(LOG_TAG, "Error:session close failed with status %d", status);
}
for (int32_t i = 0; i < mDevices.size(); i++) {
status = mDevices[i]->close();
if (0 != status) {
PAL_ERR(LOG_TAG, "Error:device close is failed with status %d", status);
}
}
PAL_VERBOSE(LOG_TAG, "closed the devices successfully");
currentState = STREAM_IDLE;
mStreamMutex.unlock();
PAL_DBG(LOG_TAG, "Exit. closed the stream successfully %d status %d",
currentState, status);
return status;
}
//TBD: move this to Stream, why duplicate code?
int32_t StreamCommon::start()
{
int32_t status = 0;
PAL_DBG(LOG_TAG, "Enter. session handle - %pK mStreamAttr->direction - %d state %d",
session, mStreamAttr->direction, currentState);
mStreamMutex.lock();
if (rm->cardState == CARD_STATUS_OFFLINE) {
cachedState = STREAM_STARTED;
PAL_ERR(LOG_TAG, "Error:Sound card offline. Update the cached state %d",
cachedState);
goto exit;
}
if (currentState == STREAM_INIT || currentState == STREAM_STOPPED) {
rm->lockGraph();
status = start_device();
if (0 != status) {
rm->unlockGraph();
goto exit;
}
PAL_VERBOSE(LOG_TAG, "device started successfully");
status = startSession();
if (0 != status) {
rm->unlockGraph();
goto exit;
}
rm->unlockGraph();
PAL_VERBOSE(LOG_TAG, "session start successful");
/*pcm_open and pcm_start done at once here,
*so directly jump to STREAM_STARTED state.
*/
currentState = STREAM_STARTED;
mStreamMutex.unlock();
rm->lockActiveStream();
mStreamMutex.lock();
for (int i = 0; i < mDevices.size(); i++) {
rm->registerDevice(mDevices[i], this);
}
rm->unlockActiveStream();
} else if (currentState == STREAM_STARTED) {
PAL_INFO(LOG_TAG, "Stream already started, state %d", currentState);
} else {
PAL_ERR(LOG_TAG, "Error:Stream is not opened yet");
status = -EINVAL;
}
exit:
PAL_DBG(LOG_TAG, "Exit. state %d", currentState);
mStreamMutex.unlock();
return status;
}
int32_t StreamCommon::start_device()
{
int32_t status = 0;
for (int32_t i=0; i < mDevices.size(); i++) {
status = mDevices[i]->start();
if (0 != status) {
PAL_ERR(LOG_TAG, "Error:%s device start is failed with status %d",
GET_DIR_STR(mStreamAttr->direction), status);
}
}
return status;
}
int32_t StreamCommon::startSession()
{
int32_t status = 0, devStatus = 0;
status = session->prepare(this);
if (0 != status) {
PAL_ERR(LOG_TAG, "Error:%s session prepare is failed with status %d",
GET_DIR_STR(mStreamAttr->direction), status);
goto session_fail;
}
PAL_VERBOSE(LOG_TAG, "session prepare successful");
status = session->start(this);
if (errno == -ENETRESET) {
if (rm->cardState != CARD_STATUS_OFFLINE) {
PAL_ERR(LOG_TAG, "Error:Sound card offline, informing RM");
rm->ssrHandler(CARD_STATUS_OFFLINE);
}
cachedState = STREAM_STARTED;
status = 0;
goto session_fail;
}
if (0 != status) {
PAL_ERR(LOG_TAG, "Error:%s session start is failed with status %d",
GET_DIR_STR(mStreamAttr->direction), status);
goto session_fail;
}
goto exit;
session_fail:
for (int32_t i=0; i < mDevices.size(); i++) {
devStatus = mDevices[i]->stop();
if (devStatus)
status = devStatus;
}
exit:
return status;
}
//TBD: move this to Stream, why duplicate code?
int32_t StreamCommon::stop()
{
int32_t status = 0;
mStreamMutex.lock();
PAL_DBG(LOG_TAG, "Enter. session handle - %pK mStreamAttr->direction - %d state %d",
session, mStreamAttr->direction, currentState);
if (currentState == STREAM_STARTED || currentState == STREAM_PAUSED) {
mStreamMutex.unlock();
rm->lockActiveStream();
mStreamMutex.lock();
currentState = STREAM_STOPPED;
for (int i = 0; i < mDevices.size(); i++) {
rm->deregisterDevice(mDevices[i], this);
}
rm->unlockActiveStream();
PAL_VERBOSE(LOG_TAG, "In %s, device count - %zu",
GET_DIR_STR(mStreamAttr->direction), mDevices.size());
rm->lockGraph();
status = session->stop(this);
if (0 != status) {
PAL_ERR(LOG_TAG, "Error:%s session stop failed with status %d",
GET_DIR_STR(mStreamAttr->direction), status);
}
PAL_VERBOSE(LOG_TAG, "session stop successful");
for (int32_t i=0; i < mDevices.size(); i++) {
status = mDevices[i]->stop();
if (0 != status) {
PAL_ERR(LOG_TAG, "Error:%s device stop failed with status %d",
GET_DIR_STR(mStreamAttr->direction), status);
}
}
rm->unlockGraph();
PAL_VERBOSE(LOG_TAG, "devices stop successful");
} else if (currentState == STREAM_STOPPED || currentState == STREAM_IDLE) {
PAL_INFO(LOG_TAG, "Stream is already in Stopped state %d", currentState);
} else {
PAL_ERR(LOG_TAG, "Error:Stream should be in start/pause state, %d", currentState);
status = -EINVAL;
}
PAL_DBG(LOG_TAG, "Exit. status %d, state %d", status, currentState);
mStreamMutex.unlock();
return status;
}
int32_t StreamCommon::setVolume(struct pal_volume_data *volume)
{
int32_t status = 0;
uint8_t volSize = 0;
PAL_DBG(LOG_TAG, "Enter. session handle - %pK", session);
if (!volume || (volume->no_of_volpair == 0)) {
PAL_ERR(LOG_TAG, "Invalid arguments");
status = -EINVAL;
goto exit;
}
// if already allocated free and reallocate
if (mVolumeData) {
free(mVolumeData);
mVolumeData = NULL;
}
volSize = sizeof(uint32_t) + (sizeof(struct pal_channel_vol_kv) * (volume->no_of_volpair));
mVolumeData = (struct pal_volume_data *)calloc(1, volSize);
if (!mVolumeData) {
status = -ENOMEM;
PAL_ERR(LOG_TAG, "failed to calloc for volume data");
goto exit;
}
/* Allow caching of stream volume as part of mVolumeData
* till the pcm_open is not done or if sound card is offline.
*/
ar_mem_cpy(mVolumeData, volSize, volume, volSize);
for (int32_t i=0; i < (mVolumeData->no_of_volpair); i++) {
PAL_INFO(LOG_TAG, "Volume payload mask:%x vol:%f",
(mVolumeData->volume_pair[i].channel_mask), (mVolumeData->volume_pair[i].vol));
}
if (a2dpMuted) {
PAL_DBG(LOG_TAG, "a2dp muted, just cache volume update");
goto exit;
}
if ((rm->cardState == CARD_STATUS_ONLINE) && (currentState != STREAM_IDLE)
&& (currentState != STREAM_INIT)) {
status = session->setConfig(this, CALIBRATION, TAG_STREAM_VOLUME);
if (0 != status) {
PAL_ERR(LOG_TAG, "session setConfig for VOLUME_TAG failed with status %d",
status);
goto exit;
}
}
exit:
if (volume) {
PAL_DBG(LOG_TAG, "Exit. Volume payload No.of vol pair:%d ch mask:%x gain:%f",
(volume->no_of_volpair), (volume->volume_pair->channel_mask),
(volume->volume_pair->vol));
}
return status;
}
int32_t StreamCommon::registerCallBack(pal_stream_callback cb, uint64_t cookie)
{
callback_ = cb;
cookie_ = cookie;
PAL_VERBOSE(LOG_TAG, "callback_ = %pK", callback_);
return 0;
}
int32_t StreamCommon::getTagsWithModuleInfo(size_t *size, uint8_t *payload)
{
int32_t status = -EINVAL;
PAL_DBG(LOG_TAG, "Enter");
if (!payload) {
PAL_ERR(LOG_TAG, "payload is NULL");
goto exit;
}
if (session)
status = session->getTagsWithModuleInfo(this, size, payload);
else
PAL_ERR(LOG_TAG, "session handle is NULL");
exit:
return status;
}
int32_t StreamCommon::ssrDownHandler()
{
int32_t status = 0;
mStreamMutex.lock();
/* Updating cached state here only if it's STREAM_IDLE,
* Otherwise we can assume it is updated by hal thread
* already.
*/
if (cachedState == STREAM_IDLE)
cachedState = currentState;
PAL_DBG(LOG_TAG, "Enter. session handle - %pK cached State %d",
session, cachedState);
switch (currentState) {
case STREAM_INIT:
case STREAM_STOPPED:
mStreamMutex.unlock();
status = close();
if (0 != status) {
PAL_ERR(LOG_TAG, "Error:stream close failed. status %d", status);
goto exit;
}
break;
case STREAM_STARTED:
case STREAM_PAUSED:
mStreamMutex.unlock();
rm->unlockActiveStream();
status = stop();
rm->lockActiveStream();
if (0 != status)
PAL_ERR(LOG_TAG, "Error:stream stop failed. status %d", status);
status = close();
if (0 != status) {
PAL_ERR(LOG_TAG, "Error:stream close failed. status %d", status);
goto exit;
}
break;
default:
PAL_ERR(LOG_TAG, "Error:stream state is %d, nothing to handle", currentState);
mStreamMutex.unlock();
goto exit;
}
exit :
PAL_DBG(LOG_TAG, "Exit, status %d", status);
currentState = STREAM_IDLE;
return status;
}
int32_t StreamCommon::ssrUpHandler()
{
int32_t status = 0;
mStreamMutex.lock();
PAL_DBG(LOG_TAG, "Enter. session handle - %pK state %d",
session, cachedState);
switch (cachedState) {
case STREAM_INIT:
mStreamMutex.unlock();
status = open();
if (0 != status) {
PAL_ERR(LOG_TAG, "Error:stream open failed. status %d", status);
goto exit;
}
break;
case STREAM_STARTED:
case STREAM_PAUSED:
{
mStreamMutex.unlock();
status = open();
if (0 != status) {
PAL_ERR(LOG_TAG, "Error:stream open failed. status %d", status);
goto exit;
}
rm->unlockActiveStream();
status = start();
rm->lockActiveStream();
if (0 != status) {
PAL_ERR(LOG_TAG, "Error:stream start failed. status %d", status);
goto exit;
}
/* For scenario when we get SSR down while handling SSR up,
* status will be 0, so we need to have this additonal check
* to keep the cached state as STREAM_STARTED.
*/
if (currentState != STREAM_STARTED) {
goto exit;
}
}
break;
default:
mStreamMutex.unlock();
PAL_ERR(LOG_TAG, "Error:stream not in correct state to handle %d", cachedState);
break;
}
cachedState = STREAM_IDLE;
exit :
PAL_DBG(LOG_TAG, "Exit, status %d", status);
return status;
}