CRAS: hfp - Consolidate SCO connection functions

The cras_hfp_alsa_iodev path requires SCO connection
setup but doesn't do HCI SCO packets read/write.

This change refactors the functions of cras_sco in
a few ways:
(1) pass cras_sco instance to cras_hfp_alsa_iodev
constructor and tracks its reference through
add/remove dev just like how cras_hfp_iodev does it.
(2) expose set/get_fd for cras_hfp_alsa_iodev usage

With above we can remove the old cras_bt_device_get/put
SCO function and consolidate offloading and non-offloading
path to use the single cras_bt_device_connect_sco APi.
This will benefit future change to extend SCO connection
configuration.

BUG=b:183594508
TEST=emerge, unittest

Change-Id: I0a2e87f2d61b1c80aa485ff28e029162e5e7f135
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/adhd/+/2853171
Reviewed-by: Hsinyu Chao <hychao@chromium.org>
Commit-Queue: Hsinyu Chao <hychao@chromium.org>
Tested-by: Hsinyu Chao <hychao@chromium.org>
diff --git a/cras/src/server/cras_bt_device.c b/cras/src/server/cras_bt_device.c
index 7c77b5b..2a577b1 100644
--- a/cras/src/server/cras_bt_device.c
+++ b/cras/src/server/cras_bt_device.c
@@ -89,8 +89,6 @@
  *    suspend_timer - The timer used to suspend device.
  *    switch_profile_timer - The timer used to delay enabling iodev after
  *        profile switch.
- *    sco_fd - The file descriptor of the SCO connection.
- *    sco_ref_count - The reference counts of the SCO connection.
  *    suspend_reason - The reason code for why suspend is scheduled.
  *    stable_id - The unique and persistent id of this bt_device.
  */
@@ -114,8 +112,6 @@
 	struct cras_timer *conn_watch_timer;
 	struct cras_timer *suspend_timer;
 	struct cras_timer *switch_profile_timer;
-	int sco_fd;
-	size_t sco_ref_count;
 	enum cras_bt_device_suspend_reason suspend_reason;
 	unsigned int stable_id;
 
@@ -1373,24 +1369,3 @@
 	iodev->active_node->volume = volume;
 	cras_iodev_list_notify_node_volume(iodev->active_node);
 }
-
-int cras_bt_device_get_sco(struct cras_bt_device *device, int codec)
-{
-	if (device->sco_ref_count == 0) {
-		device->sco_fd = cras_bt_device_sco_connect(device, codec);
-		if (device->sco_fd < 0)
-			return device->sco_fd;
-	}
-
-	++device->sco_ref_count;
-	return 0;
-}
-
-void cras_bt_device_put_sco(struct cras_bt_device *device)
-{
-	if (device->sco_ref_count == 0)
-		return;
-
-	if (--device->sco_ref_count == 0)
-		close(device->sco_fd);
-}
diff --git a/cras/src/server/cras_hfp_ag_profile.c b/cras/src/server/cras_hfp_ag_profile.c
index d0581f9..9e5de92 100644
--- a/cras/src/server/cras_hfp_ag_profile.c
+++ b/cras/src/server/cras_hfp_ag_profile.c
@@ -291,6 +291,7 @@
 	if (ag->idev)
 		return 0;
 
+	ag->sco = cras_sco_create();
 	if (need_go_sco_pcm(device)) {
 		struct cras_iodev *in_aio, *out_aio;
 
@@ -298,11 +299,10 @@
 		out_aio = cras_iodev_list_get_sco_pcm_iodev(CRAS_STREAM_OUTPUT);
 
 		ag->idev = hfp_alsa_iodev_create(in_aio, ag->device,
-						 ag->slc_handle);
+						 ag->slc_handle, ag->sco);
 		ag->odev = hfp_alsa_iodev_create(out_aio, ag->device,
-						 ag->slc_handle);
+						 ag->slc_handle, ag->sco);
 	} else {
-		ag->sco = cras_sco_create();
 		cras_sco_set_wbs_logger(ag->sco, &wbs_logger);
 		ag->idev = hfp_iodev_create(CRAS_STREAM_INPUT, ag->device,
 					    ag->slc_handle, ag->sco);
diff --git a/cras/src/server/cras_hfp_alsa_iodev.c b/cras/src/server/cras_hfp_alsa_iodev.c
index 1615e51..c563d1e 100644
--- a/cras/src/server/cras_hfp_alsa_iodev.c
+++ b/cras/src/server/cras_hfp_alsa_iodev.c
@@ -24,12 +24,14 @@
  *    device - The corresponding remote BT device.
  *    slc - The service level connection.
  *    aio - The effective iodev for playback/capture.
+ *    sco - The cras_sco instance for configuring audio path.
  */
 struct hfp_alsa_io {
 	struct cras_iodev base;
 	struct cras_bt_device *device;
 	struct hfp_slc_handle *slc;
 	struct cras_iodev *aio;
+	struct cras_sco *sco;
 };
 
 static int hfp_alsa_get_valid_frames(struct cras_iodev *iodev,
@@ -105,9 +107,17 @@
 		return rc;
 	}
 
+	/* Check the associated SCO object first. Because configuring
+	 * the shared SCO object can only be done once for the HFP
+	 * input and output devices pair.
+	 */
+	rc = cras_sco_get_fd(hfp_alsa_io->sco);
+	if (rc >= 0)
+		goto add_dev;
+
 	hfp_slc_codec_connection_setup(hfp_alsa_io->slc);
 
-	rc = cras_bt_device_get_sco(
+	rc = cras_bt_device_sco_connect(
 		hfp_alsa_io->device,
 		hfp_slc_get_selected_codec(hfp_alsa_io->slc));
 	if (rc < 0) {
@@ -115,7 +125,11 @@
 		return rc;
 	}
 
+	cras_sco_set_fd(hfp_alsa_io->sco, rc);
 	hfp_set_call_status(hfp_alsa_io->slc, 1);
+
+add_dev:
+	cras_sco_add_iodev(hfp_alsa_io->sco, iodev->direction, iodev->format);
 	iodev->buffer_size = aio->buffer_size;
 
 	return 0;
@@ -126,8 +140,16 @@
 	struct hfp_alsa_io *hfp_alsa_io = (struct hfp_alsa_io *)iodev;
 	struct cras_iodev *aio = hfp_alsa_io->aio;
 
-	hfp_set_call_status(hfp_alsa_io->slc, 0);
-	cras_bt_device_put_sco(hfp_alsa_io->device);
+	cras_sco_rm_iodev(hfp_alsa_io->sco, iodev->direction);
+
+	/* Check the associated SCO object because cleaning up the
+	 * shared SLC and SCO objects should be done when the later
+	 * of HFP input or output device gets closed.
+	 */
+	if (!cras_sco_has_iodev(hfp_alsa_io->sco)) {
+		hfp_set_call_status(hfp_alsa_io->slc, 0);
+		cras_sco_close_fd(hfp_alsa_io->sco);
+	}
 	cras_iodev_free_format(iodev);
 	return aio->close_dev(aio);
 }
@@ -243,7 +265,8 @@
 
 struct cras_iodev *hfp_alsa_iodev_create(struct cras_iodev *aio,
 					 struct cras_bt_device *device,
-					 struct hfp_slc_handle *slc)
+					 struct hfp_slc_handle *slc,
+					 struct cras_sco *sco)
 {
 	struct hfp_alsa_io *hfp_alsa_io;
 	struct cras_iodev *iodev;
@@ -259,6 +282,7 @@
 
 	hfp_alsa_io->device = device;
 	hfp_alsa_io->slc = slc;
+	hfp_alsa_io->sco = sco;
 	hfp_alsa_io->aio = aio;
 
 	/* Set iodev's name to device readable name or the address. */
diff --git a/cras/src/server/cras_hfp_alsa_iodev.h b/cras/src/server/cras_hfp_alsa_iodev.h
index e09efba..523eba8 100644
--- a/cras/src/server/cras_hfp_alsa_iodev.h
+++ b/cras/src/server/cras_hfp_alsa_iodev.h
@@ -22,7 +22,8 @@
  */
 struct cras_iodev *hfp_alsa_iodev_create(struct cras_iodev *aio,
 					 struct cras_bt_device *device,
-					 struct hfp_slc_handle *slc);
+					 struct hfp_slc_handle *slc,
+					 struct cras_sco *sco);
 
 void hfp_alsa_iodev_destroy(struct cras_iodev *iodev);
 
diff --git a/cras/src/server/cras_hfp_iodev.c b/cras/src/server/cras_hfp_iodev.c
index ad2f6d4..ad123c6 100644
--- a/cras/src/server/cras_hfp_iodev.c
+++ b/cras/src/server/cras_hfp_iodev.c
@@ -153,11 +153,12 @@
 	if (sk < 0)
 		goto error;
 
+	cras_sco_set_fd(hfpio->sco, sk);
 	mtu = cras_bt_device_sco_packet_size(
 		hfpio->device, sk, hfp_slc_get_selected_codec(hfpio->slc));
 
 	/* Start cras_sco */
-	err = cras_sco_start(sk, mtu, hfp_slc_get_selected_codec(hfpio->slc),
+	err = cras_sco_start(mtu, hfp_slc_get_selected_codec(hfpio->slc),
 			     hfpio->sco);
 	if (err)
 		goto error;
@@ -183,6 +184,7 @@
 	cras_sco_rm_iodev(hfpio->sco, iodev->direction);
 	if (cras_sco_running(hfpio->sco) && !cras_sco_has_iodev(hfpio->sco)) {
 		cras_sco_stop(hfpio->sco);
+		cras_sco_close_fd(hfpio->sco);
 		hfp_set_call_status(hfpio->slc, 0);
 	}
 
diff --git a/cras/src/server/cras_sco.c b/cras/src/server/cras_sco.c
index 4e0306d..9300d4c 100644
--- a/cras/src/server/cras_sco.c
+++ b/cras/src/server/cras_sco.c
@@ -755,14 +755,38 @@
 	sco->wbs_logger = wbs_logger;
 }
 
+int cras_sco_set_fd(struct cras_sco *sco, int fd)
+{
+	/* Valid only when existing fd isn't set and the new fd is
+	 * non-negative to prevent leak. */
+	if (sco->fd >= 0 || fd < 0)
+		return -EINVAL;
+	sco->fd = fd;
+	return 0;
+}
+
+int cras_sco_get_fd(struct cras_sco *sco)
+{
+	return sco->fd;
+}
+
+void cras_sco_close_fd(struct cras_sco *sco)
+{
+	if (sco->fd < 0)
+		return;
+	close(sco->fd);
+	sco->fd = -1;
+}
+
 int cras_sco_running(struct cras_sco *sco)
 {
 	return sco->started;
 }
 
-int cras_sco_start(int fd, unsigned int mtu, int codec, struct cras_sco *sco)
+int cras_sco_start(unsigned int mtu, int codec, struct cras_sco *sco)
 {
-	sco->fd = fd;
+	if (sco->fd == 0)
+		return -EINVAL;
 	sco->mtu = mtu;
 
 	/* Initialize to MTU, it may change when actually read the socket. */
@@ -826,9 +850,6 @@
 
 	audio_thread_rm_callback_sync(cras_iodev_list_get_audio_thread(),
 				      sco->fd);
-
-	close(sco->fd);
-	sco->fd = -1;
 	sco->started = 0;
 
 	/* Unset the write/read callbacks. */
diff --git a/cras/src/server/cras_sco.h b/cras/src/server/cras_sco.h
index 05eb1c5..3674134 100644
--- a/cras/src/server/cras_sco.h
+++ b/cras/src/server/cras_sco.h
@@ -34,15 +34,26 @@
 void cras_sco_set_wbs_logger(struct cras_sco *sco,
 			     struct packet_status_logger *wbs_logger);
 
+/* Sets the file descriptor to cras_sco. */
+int cras_sco_set_fd(struct cras_sco *sco, int fd);
+
+/* Gets the file descriptor of cras_sco. */
+int cras_sco_get_fd(struct cras_sco *sco);
+
+/* Closes the file descriptor of cras_sco. */
+void cras_sco_close_fd(struct cras_sco *sco);
+
 /* Checks if given cras_sco is running. */
 int cras_sco_running(struct cras_sco *sco);
 
 /* Starts the cras_sco to transmit and reveice samples to and from the file
  * descriptor of a SCO socket. This should be called from main thread.
  * Args:
+ *    mtu - The packet size of HCI SCO packet.
  *    codec - 1 for CVSD, 2 for mSBC per HFP 1.7 specification.
+ *    sco - The cras_sco instance.
  */
-int cras_sco_start(int fd, unsigned int mtu, int codec, struct cras_sco *sco);
+int cras_sco_start(unsigned int mtu, int codec, struct cras_sco *sco);
 
 /* Stops given cras_sco. This implies sample transmission will
  * stop and socket be closed. This should be called from main thread.
diff --git a/cras/src/tests/cras_sco_unittest.cc b/cras/src/tests/cras_sco_unittest.cc
index f96d034..353edb5 100644
--- a/cras/src/tests/cras_sco_unittest.cc
+++ b/cras/src/tests/cras_sco_unittest.cc
@@ -79,13 +79,17 @@
 TEST(CrasSco, AcquirePlaybackBuffer) {
   unsigned buffer_frames, buffer_frames2, queued;
   uint8_t* samples;
+  int sock[2];
 
   ResetStubData();
 
+  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
+
   sco = cras_sco_create();
   ASSERT_NE(sco, (void*)NULL);
 
-  cras_sco_start(1, 48, HFP_CODEC_ID_CVSD, sco);
+  cras_sco_set_fd(sco, sock[1]);
+  cras_sco_start(48, HFP_CODEC_ID_CVSD, sco);
   dev.direction = CRAS_STREAM_OUTPUT;
   ASSERT_EQ(0, cras_sco_add_iodev(sco, dev.direction, dev.format));
 
@@ -121,19 +125,24 @@
 
   ASSERT_GE(sco->playback_buf->used_size / 2, buffer_frames + buffer_frames2);
 
+  cras_sco_stop(sco);
+  cras_sco_close_fd(sco);
   cras_sco_destroy(sco);
 }
 
 TEST(CrasSco, AcquireCaptureBuffer) {
   unsigned buffer_frames, buffer_frames2;
   uint8_t* samples;
+  int sock[2];
 
   ResetStubData();
 
+  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
   sco = cras_sco_create();
   ASSERT_NE(sco, (void*)NULL);
 
-  cras_sco_start(1, 48, HFP_CODEC_ID_CVSD, sco);
+  cras_sco_set_fd(sco, sock[1]);
+  cras_sco_start(48, HFP_CODEC_ID_CVSD, sco);
   dev.direction = CRAS_STREAM_INPUT;
   ASSERT_EQ(0, cras_sco_add_iodev(sco, dev.direction, dev.format));
 
@@ -164,6 +173,8 @@
 
   ASSERT_GE(sco->capture_buf->used_size / 2, buffer_frames + buffer_frames2);
 
+  cras_sco_stop(sco);
+  cras_sco_close_fd(sco);
   cras_sco_destroy(sco);
 }
 
@@ -182,7 +193,8 @@
   ASSERT_NE(sco, (void*)NULL);
 
   dev.direction = CRAS_STREAM_INPUT;
-  cras_sco_start(sock[1], 48, HFP_CODEC_ID_CVSD, sco);
+  cras_sco_set_fd(sco, sock[1]);
+  cras_sco_start(48, HFP_CODEC_ID_CVSD, sco);
   ASSERT_EQ(0, cras_sco_add_iodev(sco, dev.direction, dev.format));
 
   /* Mock the sco fd and send some fake data */
@@ -221,6 +233,7 @@
   rc = recv(sock[0], sample, 48, 0);
   ASSERT_EQ(48, rc);
 
+  cras_sco_close_fd(sco);
   cras_sco_destroy(sco);
 }
 
@@ -232,7 +245,8 @@
   sco = cras_sco_create();
   ASSERT_NE(sco, (void*)NULL);
 
-  cras_sco_start(sock[0], 48, HFP_CODEC_ID_CVSD, sco);
+  cras_sco_set_fd(sco, sock[0]);
+  cras_sco_start(48, HFP_CODEC_ID_CVSD, sco);
   ASSERT_EQ(1, cras_sco_running(sco));
   ASSERT_EQ(cb_data, (void*)sco);
 
@@ -240,6 +254,7 @@
   ASSERT_EQ(0, cras_sco_running(sco));
   ASSERT_EQ(NULL, cb_data);
 
+  cras_sco_close_fd(sco);
   cras_sco_destroy(sco);
 }
 
@@ -256,7 +271,8 @@
   ASSERT_NE(sco, (void*)NULL);
 
   /* Start and send two chunk of fake data */
-  cras_sco_start(sock[1], 48, HFP_CODEC_ID_CVSD, sco);
+  cras_sco_set_fd(sco, sock[1]);
+  cras_sco_start(48, HFP_CODEC_ID_CVSD, sco);
   send(sock[0], sample, 48, 0);
   send(sock[0], sample, 48, 0);
 
@@ -285,6 +301,7 @@
   cras_sco_stop(sco);
   ASSERT_EQ(0, cras_sco_running(sco));
 
+  cras_sco_close_fd(sco);
   cras_sco_destroy(sco);
 }
 
@@ -300,7 +317,8 @@
   sco = cras_sco_create();
   ASSERT_NE(sco, (void*)NULL);
 
-  cras_sco_start(sock[1], 48, HFP_CODEC_ID_CVSD, sco);
+  cras_sco_set_fd(sco, sock[1]);
+  cras_sco_start(48, HFP_CODEC_ID_CVSD, sco);
   send(sock[0], sample, 48, 0);
   send(sock[0], sample, 48, 0);
 
@@ -327,6 +345,7 @@
   ASSERT_EQ(480, cras_sco_buf_queued(sco, dev.direction));
 
   cras_sco_stop(sco);
+  cras_sco_close_fd(sco);
   cras_sco_destroy(sco);
 }
 
@@ -388,7 +407,8 @@
   ASSERT_EQ(0, cras_msbc_plc_create_called);
 
   /* Start and send an mSBC packets with all zero samples */
-  cras_sco_start(sock[1], 63, HFP_CODEC_ID_MSBC, sco);
+  cras_sco_set_fd(sco, sock[1]);
+  cras_sco_start(63, HFP_CODEC_ID_MSBC, sco);
   ASSERT_EQ(2, get_msbc_codec_create_called());
   ASSERT_EQ(1, cras_msbc_plc_create_called);
   send_mSBC_packet(sock[0], pkt_count++, 0);
@@ -467,6 +487,7 @@
   cras_sco_stop(sco);
   ASSERT_EQ(0, cras_sco_running(sco));
 
+  cras_sco_close_fd(sco);
   cras_sco_destroy(sco);
 }
 
@@ -483,7 +504,8 @@
   sco = cras_sco_create();
   ASSERT_NE(sco, (void*)NULL);
 
-  cras_sco_start(sock[1], 63, HFP_CODEC_ID_MSBC, sco);
+  cras_sco_set_fd(sco, sock[1]);
+  cras_sco_start(63, HFP_CODEC_ID_MSBC, sco);
   send(sock[0], sample, 63, 0);
 
   /* Trigger thread callback */
@@ -506,6 +528,7 @@
   ASSERT_EQ(0, cras_sco_buf_queued(sco, dev.direction));
 
   cras_sco_stop(sco);
+  cras_sco_close_fd(sco);
   cras_sco_destroy(sco);
 }
 
diff --git a/cras/src/tests/hfp_alsa_iodev_unittest.cc b/cras/src/tests/hfp_alsa_iodev_unittest.cc
index ff4f8e1..e7e60bc 100644
--- a/cras/src/tests/hfp_alsa_iodev_unittest.cc
+++ b/cras/src/tests/hfp_alsa_iodev_unittest.cc
@@ -19,6 +19,7 @@
   struct cras_iodev* aio;
 };
 
+static struct cras_sco* fake_sco;
 static struct cras_iodev fake_sco_out, fake_sco_in;
 static struct cras_bt_device* fake_device;
 static struct hfp_slc_handle* fake_slc;
@@ -82,6 +83,7 @@
   hfp_set_call_status_called = 0;
   hfp_event_speaker_gain_called = 0;
 
+  fake_sco = reinterpret_cast<struct cras_sco*>(0x123);
   fake_sco_out.open_dev = fake_sco_in.open_dev =
       (int (*)(struct cras_iodev*))fake_open_dev;
   fake_open_dev_called = 0;
@@ -157,7 +159,7 @@
   struct hfp_alsa_io* hfp_alsa_io;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   hfp_alsa_io = (struct hfp_alsa_io*)iodev;
 
   EXPECT_EQ(CRAS_STREAM_OUTPUT, iodev->direction);
@@ -178,7 +180,7 @@
   struct hfp_alsa_io* hfp_alsa_io;
 
   fake_sco_in.direction = CRAS_STREAM_INPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_in, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_in, fake_device, fake_slc, fake_sco);
   hfp_alsa_io = (struct hfp_alsa_io*)iodev;
 
   EXPECT_EQ(CRAS_STREAM_INPUT, iodev->direction);
@@ -200,7 +202,7 @@
   struct cras_iodev* iodev;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   iodev->open_dev(iodev);
 
   EXPECT_EQ(1, fake_open_dev_called);
@@ -220,7 +222,7 @@
   fake_sco_out.supported_formats = supported_formats;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   iodev->update_supported_formats(iodev);
 
   // update_supported_format on alsa_io is not called.
@@ -241,7 +243,7 @@
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
   fake_sco_out.buffer_size = buf_size;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   hfp_alsa_io = (struct hfp_alsa_io*)iodev;
   iodev->format = &fake_format;
   iodev->configure_dev(iodev);
@@ -264,7 +266,7 @@
   struct cras_iodev* iodev;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   iodev->close_dev(iodev);
 
   EXPECT_EQ(1, hfp_set_call_status_called);
@@ -278,7 +280,7 @@
   struct cras_iodev* iodev;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   iodev->frames_queued(iodev, (struct timespec*)NULL);
 
   EXPECT_EQ(1, fake_frames_queued_called);
@@ -290,7 +292,7 @@
   struct cras_iodev* iodev;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   iodev->delay_frames(iodev);
 
   EXPECT_EQ(1, fake_delay_frames_called);
@@ -302,7 +304,7 @@
   struct cras_iodev* iodev;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   iodev->get_buffer(iodev, (struct cras_audio_area**)NULL, (unsigned*)NULL);
 
   EXPECT_EQ(1, fake_get_buffer_called);
@@ -314,7 +316,7 @@
   struct cras_iodev* iodev;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   iodev->put_buffer(iodev, 0xdeadbeef);
 
   EXPECT_EQ(1, fake_put_buffer_called);
@@ -326,7 +328,7 @@
   struct cras_iodev* iodev;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   iodev->flush_buffer(iodev);
 
   EXPECT_EQ(1, fake_flush_buffer_called);
@@ -338,7 +340,7 @@
   struct cras_iodev* iodev;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   iodev->update_active_node(iodev, 0xdeadbeef, 0xdeadbeef);
 
   EXPECT_EQ(1, fake_update_active_node_called);
@@ -350,7 +352,7 @@
   struct cras_iodev* iodev;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   iodev->start(iodev);
 
   EXPECT_EQ(1, fake_start_called);
@@ -362,7 +364,7 @@
   struct cras_iodev* iodev;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   iodev->set_volume(iodev);
 
   EXPECT_EQ(1, hfp_event_speaker_gain_called);
@@ -374,7 +376,7 @@
   struct cras_iodev* iodev;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   iodev->min_cb_level = 0xab;
   iodev->max_cb_level = 0xcd;
 
@@ -391,7 +393,7 @@
   struct cras_iodev* iodev;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   iodev->is_free_running(iodev);
 
   EXPECT_EQ(1, fake_is_free_running_called);
@@ -403,7 +405,7 @@
   struct cras_iodev* iodev;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
   iodev->min_cb_level = 0xab;
   iodev->max_cb_level = 0xcd;
 
@@ -421,7 +423,7 @@
   struct timespec ts;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
-  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc);
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc, fake_sco);
 
   iodev->get_valid_frames(iodev, &ts);
 
@@ -515,11 +517,35 @@
   return 0;
 }
 
-int cras_bt_device_get_sco(struct cras_bt_device* device, int codec) {
+int cras_bt_device_sco_connect(struct cras_bt_device* device, int codec) {
   return 0;
 }
 
-void cras_bt_device_put_sco(struct cras_bt_device* device) {}
+int cras_sco_add_iodev(struct cras_sco* sco,
+                       enum CRAS_STREAM_DIRECTION direction,
+                       struct cras_audio_format* format) {
+  return 0;
+}
+
+int cras_sco_rm_iodev(struct cras_sco* sco,
+                      enum CRAS_STREAM_DIRECTION direction) {
+  return 0;
+}
+
+int cras_sco_has_iodev(struct cras_sco* sco) {
+  return 0;
+}
+
+int cras_sco_set_fd(struct cras_sco* sco, int fd) {
+  return 0;
+}
+
+int cras_sco_get_fd(struct cras_sco* sco) {
+  return -1;
+}
+void cras_sco_close_fd(struct cras_sco* sco) {
+  return;
+}
 
 int hfp_slc_get_selected_codec(struct hfp_slc_handle* handle) {
   return HFP_CODEC_ID_CVSD;
diff --git a/cras/src/tests/hfp_iodev_unittest.cc b/cras/src/tests/hfp_iodev_unittest.cc
index 339b3f7..ec21e68 100644
--- a/cras/src/tests/hfp_iodev_unittest.cc
+++ b/cras/src/tests/hfp_iodev_unittest.cc
@@ -307,7 +307,7 @@
   return cras_sco_running_return_val;
 }
 
-int cras_sco_start(int fd, unsigned int mtu, int codec, struct cras_sco* sco) {
+int cras_sco_start(unsigned int mtu, int codec, struct cras_sco* sco) {
   cras_sco_start_called++;
   return 0;
 }
@@ -317,6 +317,13 @@
   return 0;
 }
 
+int cras_sco_set_fd(struct cras_sco* sco, int fd) {
+  return 0;
+}
+void cras_sco_close_fd(struct cras_sco* sco) {
+  return;
+}
+
 int cras_sco_buf_queued(struct cras_sco* sco,
                         const enum CRAS_STREAM_DIRECTION direction) {
   return 0;