aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordvdli <dvdli@google.com>2021-06-02 22:05:01 +0800
committerdvdli <dvdli@google.com>2021-06-02 23:11:36 +0800
commitea4546b237269915e832fd74a9883a2dadf46093 (patch)
tree7cf42df6c5c17b207be376eb00fe91d42bd9e352
parent36f9078ef5410279725207daa00943ee48b05253 (diff)
force pcm_open to open device with the non-blocking flag
When a client opens a PCM device whose substreams are all occupied without the non-blocking flag, the open function would be blocked in the kernel until the previous opened ones are closed. This would cause deadlock if they try to hold the same lock. Most of the ALSA PCM drivers on embedded systems are implemented with the ALSA SOC framework. Each PCM device has only one substream. This problem would happen frequently. To force pcm_open to open PCM devices with non-blocking flag is beneficial to resolve this problem. It returns the control to clients to try again later. The reason why we don't call pcm_open with PCM_NONBLOCK is that the PCM_NONBLOCK also affects the read and write behaviors. I also add a test case to test whether the pcm_open would be blocked.
-rw-r--r--src/pcm_hw.c17
-rw-r--r--tests/include/pcm_test_device.h24
-rw-r--r--tests/src/pcm_loopback_test.cc8
-rw-r--r--tests/src/pcm_test.cc92
4 files changed, 109 insertions, 32 deletions
diff --git a/src/pcm_hw.c b/src/pcm_hw.c
index 38b2e83..4792895 100644
--- a/src/pcm_hw.c
+++ b/src/pcm_hw.c
@@ -111,16 +111,25 @@ static int pcm_hw_open(unsigned int card, unsigned int device,
snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
flags & PCM_IN ? 'c' : 'p');
- if (flags & PCM_NONBLOCK)
- fd = open(fn, O_RDWR|O_NONBLOCK);
- else
- fd = open(fn, O_RDWR);
+ // Open the device with non-blocking flag to avoid to be blocked in kernel when all of the
+ // substreams of this PCM device are opened by others.
+ fd = open(fn, O_RDWR | O_NONBLOCK);
if (fd < 0) {
free(hw_data);
return fd;
}
+ if ((flags & PCM_NONBLOCK) == 0) {
+ // Set the file descriptor to blocking mode.
+ if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK) < 0) {
+ fprintf(stderr, "failed to set to blocking mode on %s", fn);
+ close(fd);
+ free(hw_data);
+ return -ENODEV;
+ }
+ }
+
hw_data->card = card;
hw_data->device = device;
hw_data->fd = fd;
diff --git a/tests/include/pcm_test_device.h b/tests/include/pcm_test_device.h
index 7ced192..a0ea811 100644
--- a/tests/include/pcm_test_device.h
+++ b/tests/include/pcm_test_device.h
@@ -29,6 +29,8 @@
#ifndef TINYALSA_TESTS_PCM_TEST_H_
#define TINYALSA_TESTS_PCM_TEST_H_
+#include "tinyalsa/pcm.h"
+
namespace tinyalsa {
namespace testing {
@@ -44,9 +46,25 @@ namespace testing {
#define TEST_LOOPBACK_CAPTURE_DEVICE 1
#endif
-constexpr unsigned int kLoopbackCard = TEST_LOOPBACK_CARD;
-constexpr unsigned int kLoopbackPlaybackDevice = TEST_LOOPBACK_PLAYBACK_DEVICE;
-constexpr unsigned int kLoopbackCaptureDevice = TEST_LOOPBACK_CAPTURE_DEVICE;
+static constexpr unsigned int kLoopbackCard = TEST_LOOPBACK_CARD;
+static constexpr unsigned int kLoopbackPlaybackDevice = TEST_LOOPBACK_PLAYBACK_DEVICE;
+static constexpr unsigned int kLoopbackCaptureDevice = TEST_LOOPBACK_CAPTURE_DEVICE;
+
+static constexpr unsigned int kDefaultChannels = 2;
+static constexpr unsigned int kDefaultSamplingRate = 48000;
+static constexpr unsigned int kDefaultPeriodSize = 1024;
+static constexpr unsigned int kDefaultPeriodCount = 3;
+static constexpr pcm_config kDefaultConfig = {
+ .channels = kDefaultChannels,
+ .rate = kDefaultSamplingRate,
+ .period_size = kDefaultPeriodSize,
+ .period_count = kDefaultPeriodCount,
+ .format = PCM_FORMAT_S16_LE,
+ .start_threshold = kDefaultPeriodSize,
+ .stop_threshold = kDefaultPeriodSize * kDefaultPeriodCount,
+ .silence_threshold = 0,
+ .silence_size = 0,
+};
} // namespace testing
} // namespace tinyalsa
diff --git a/tests/src/pcm_loopback_test.cc b/tests/src/pcm_loopback_test.cc
index d48b319..3da3231 100644
--- a/tests/src/pcm_loopback_test.cc
+++ b/tests/src/pcm_loopback_test.cc
@@ -193,10 +193,6 @@ class PcmLoopbackTest : public ::testing::Test {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
- static constexpr unsigned int kDefaultChannels = 2;
- static constexpr unsigned int kDefaultSamplingRate = 48000;
- static constexpr unsigned int kDefaultPeriodSize = 1024;
- static constexpr unsigned int kDefaultPeriodCount = 3;
static constexpr unsigned int kDefaultPeriodTimeInMs =
kDefaultPeriodSize * 1000 / kDefaultSamplingRate;
static constexpr pcm_format kPcmForamt = F::kFormat;
@@ -212,10 +208,6 @@ using Formats = ::testing::Types<S16bitlePcmFormat, FloatPcmFormat>;
TYPED_TEST_SUITE(PcmLoopbackTest, Formats);
TYPED_TEST(PcmLoopbackTest, Loopback) {
- static constexpr unsigned int kDefaultChannels = this->kDefaultChannels;
- static constexpr unsigned int kDefaultSamplingRate = this->kDefaultSamplingRate;
- static constexpr unsigned int kDefaultPeriodSize = this->kDefaultPeriodSize;
- // static constexpr unsigned int kDefaultPeriodCount = this->kDefaultPeriodCount;
static constexpr unsigned int kDefaultPeriodTimeInMs = this->kDefaultPeriodTimeInMs;
static constexpr pcm_format kPcmForamt = this->kPcmForamt;
pcm *pcm_in = this->pcm_in;
diff --git a/tests/src/pcm_test.cc b/tests/src/pcm_test.cc
index da114de..9a14aa2 100644
--- a/tests/src/pcm_test.cc
+++ b/tests/src/pcm_test.cc
@@ -26,13 +26,20 @@
** DAMAGE.
*/
-#include <string>
+#include <cstdio>
+#include <fstream>
#include <iostream>
+#include <memory>
+#include <string_view>
+#include <string>
+#include <thread>
#include <gtest/gtest.h>
#include "tinyalsa/pcm.h"
+#include "pcm_test_device.h"
+
namespace tinyalsa {
namespace testing {
@@ -54,22 +61,6 @@ TEST(PcmTest, FormatToBits) {
}
TEST(PcmTest, OpenAndCloseOutPcm) {
- static constexpr unsigned int kDefaultChannels = 2;
- static constexpr unsigned int kDefaultSamplingRate = 48000;
- static constexpr unsigned int kDefaultPeriodSize = 1024;
- static constexpr unsigned int kDefaultPeriodCount = 3;
- static constexpr pcm_config kDefaultConfig = {
- .channels = kDefaultChannels,
- .rate = kDefaultSamplingRate,
- .period_size = kDefaultPeriodSize,
- .period_count = kDefaultPeriodCount,
- .format = PCM_FORMAT_S16_LE,
- .start_threshold = kDefaultPeriodSize,
- .stop_threshold = kDefaultPeriodSize * kDefaultPeriodCount,
- .silence_threshold = 0,
- .silence_size = 0,
- };
-
pcm *pcm_object = pcm_open(1000, 1000, PCM_OUT, &kDefaultConfig);
ASSERT_FALSE(pcm_is_ready(pcm_object));
ASSERT_EQ(pcm_close(pcm_object), 0);
@@ -101,5 +92,72 @@ TEST(PcmTest, OpenAndCloseOutPcm) {
ASSERT_EQ(pcm_close(pcm_object), 0);
}
+TEST(PcmTest, OpenWithoutBlocking) {
+ char loopback_device_info_path[120] = {};
+ snprintf(loopback_device_info_path, sizeof(loopback_device_info_path),
+ "/proc/asound/card%d/pcm%dp/info", kLoopbackCard, kLoopbackPlaybackDevice);
+
+ std::ifstream info_file_stream{loopback_device_info_path};
+ if (!info_file_stream.is_open()) {
+ GTEST_SKIP();
+ }
+
+ char buffer[256] = {};
+ int32_t subdevice_count = 0;
+ while (info_file_stream.good()) {
+ info_file_stream.getline(buffer, sizeof(buffer));
+ std::cout << buffer << std::endl;
+ std::string_view line{buffer};
+ if (line.find("subdevices_count") != std::string_view::npos) {
+ auto subdevice_count_string = line.substr(line.find(":") + 1);
+ std::cout << subdevice_count_string << std::endl;
+ subdevice_count = std::stoi(std::string{subdevice_count_string});
+ }
+ }
+
+ ASSERT_GT(subdevice_count, 0);
+
+ auto pcm_array = std::make_unique<pcm *[]>(subdevice_count);
+ std::thread *open_thread = new std::thread{[&pcm_array, subdevice_count] {
+ // Occupy all substreams
+ for (int32_t i = 0; i < subdevice_count; i++) {
+ pcm_array[i] = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT,
+ &kDefaultConfig);
+ EXPECT_TRUE(pcm_is_ready(pcm_array[i]));
+ }
+
+ // Expect that pcm_open is not blocked in the kernel and return a bad_object pointer.
+ pcm *pcm_object = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT,
+ &kDefaultConfig);
+ if (pcm_is_ready(pcm_object)) {
+ // open_thread is blocked in kernel because of the substream is all occupied. pcm_open
+ // returns because the main thread has released all pcm structures in pcm_array. We just
+ // need to close the pcm_object here.
+ pcm_close(pcm_object);
+ return;
+ }
+
+ // Release all substreams
+ for (int32_t i = 0; i < subdevice_count; i++) {
+ pcm_close(pcm_array[i]);
+ pcm_array[i] = nullptr;
+ }
+ }};
+
+ static constexpr int64_t kTimeoutMs = 500;
+ std::this_thread::sleep_for(std::chrono::milliseconds(kTimeoutMs));
+ if (pcm_array[0] == nullptr) {
+ open_thread->join();
+ } else {
+ for (int32_t i = 0; i < subdevice_count; i++) {
+ pcm_close(pcm_array[i]);
+ pcm_array[i] = nullptr;
+ }
+ open_thread->join();
+ FAIL() << "The open_thread is blocked in kernel or the kTimeoutMs(" << kTimeoutMs <<
+ ") is too short to complete";
+ }
+}
+
} // namespace testing
} // namespace tinyalsa