SCO(7)
π¦ SCO(7) π¦
okay so this is sco β the SCO transport, straight out of the Linux System Administration section of the manual. buckle up bestie, we're going into the Bluetooth audio-adjacent trenches π
πΈ NAME πΈ
sco β SCO transport
β¨ SYNOPSIS β¨
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/sco.h>
sco_socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
π DESCRIPTION π
so the SCO logical transport is this symmetric, point-to-point transport between the Central and a specific Peripheral. it reserves slots, which is why we can lowkey think of it as a circuit-switched connection between the Central and the Peripheral. very "we booked this lane in advance" of it π
and because reserved slots weren't enough of a flex β when eSCO is supported, a retransmission window follows immediately after them. together, the reserved slots and the retransmission window form the complete eSCO window. periodt. π₯
π SOCKET ADDRESS π
struct sockaddr_sco {
sa_family_t sco_family;
bdaddr_t sco_bdaddr;
};
example time β¨
struct sockaddr_sco addr;
memset(&addr, 0, sizeof(addr));
addr.sco_family = AF_BLUETOOTH;
bacpy(&addr.sco_bdaddr, bdaddr);
π SOCKET OPTIONS (SOL_BLUETOOTH) π
the socket options listed below can all be set with setsockopt(2) and read with getsockopt(2), with the socket level set to SOL_BLUETOOTH. set the level, be a good citizen, we love to see it π«Ά
π BT_SECURITY (since Linux 2.6.30)
channel security level. possible values are giving "pick your fighter":
BT_SECURITY_SDPβ security level0(SDP Only); link key type: None; encryption: Not requiredBT_SECURITY_LOWβ security level1(Low); link key type: Unauthenticated; encryption: Not requiredBT_SECURITY_MEDIUMβ security level2(Medium - default); link key type: Unauthenticated; encryption: DesiredBT_SECURITY_HIGHβ security level3(High); link key type: Authenticated; encryption: RequiredBT_SECURITY_FIPS(since Linux 3.15) β security level4(Secure Only); link key type: Authenticated (P-256 based Secure Simple Pairing and Secure Authentication); encryption: Required
example:
int level = BT_SECURITY_HIGH;
int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_SECURITY, &level,
sizeof(level));
if (err == -1) {
perror("setsockopt");
return 1;
}
π‘οΈ BT_DEFER_SETUP (since Linux 2.6.30)
channel defer connection setup. this one controls whether the connection procedure needs to be authorized by userspace before responding β which is what allows authorization at the profile level. honestly kind of a big deal π
possible values:
0β Disable (default); authorization: Not required1β Enable; authorization: Required
example:
int defer_setup = 1;
int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_DEFER_SETUP,
&defer_setup, sizeof(defer_setup));
if (err == -1) {
perror("setsockopt");
return err;
}
err = listen(sco_socket, 5);
if (err) {
perror("listen");
return err;
}
struct sockaddr_sco remote_addr = {0};
socklen_t addr_len = sizeof(remote_addr);
int new_socket = accept(sco_socket, (struct sockaddr*)&remote_addr,
&addr_len);
if (new_socket < 0) {
perror("accept");
return new_socket;
}
/* To complete the connection setup of new_socket read 1 byte */
char c;
struct pollfd pfd;
memset(&pfd, 0, sizeof(pfd));
pfd.fd = new_socket;
pfd.events = POLLOUT;
err = poll(&pfd, 1, 0);
if (err) {
perror("poll");
return err;
}
if (!(pfd.revents & POLLOUT)) {
err = read(sk, &c, 1);
if (err < 0) {
perror("read");
return err;
}
}
ποΈ BT_VOICE (since Linux 3.11)
transport voice settings. here's the little struct first, because we're classy:
struct bt_voice {
uint16_t setting;
};
and the possible values:
BT_VOICE_TRANSPARENTβ value0x0003; Transparent outputBT_VOICE_CVSD_16BITβ value0x0060; C-VSD output PCM 16-bit inputBT_VOICE_TRANSPARENT_16BITβ value0x0063; Transparent output PCM 16-bit input
example:
struct bt_voice voice;
memset(&voice, 0, sizeof(voice));
voice.setting = BT_VOICE_TRANSPARENT;
int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_VOICE, &voice,
sizeof(voice));
if (err == -1) {
perror("setsockopt");
return 1;
}
π¦ BT_PKT_STATUS (since Linux 5.9)
this one enables reporting packet status via BT_SCM_PKT_STATUS CMSG on received packets. possible values:
0β Disable (default)1β Enable
π·οΈ BT_SCM_PKT_STATUS
level SOL_BLUETOOTH CMSG with data:
uint8_t pkt_status;
the values are equal to the "Packet_Status_Flag" defined in Core Specification v6.0 Sec. 5.4.3 pp. 1877, which is a whole mood. so:
0x0β Correctly received data0x1β Possibly invalid data0x2β No data received0x3β Data partially lost
and that's the end of part one bestie, don't go anywhere π¦β¨
BT_PHY (since Linux 5.10) π‘
Which transport PHY(s) this socket supports β read-only, so likeβ¦ there's no setsockopt support for this one bestie, you can look but you cannot touch. The possible values are:
BT_PHY_BR_1M_1SLOTβBIT 0, BR 1Mbps 1SLOTBT_PHY_BR_1M_3SLOTβBIT 1, BR 1Mbps 3SLOTBT_PHY_BR_2M_1SLOTβBIT 3, EDR 2Mbps 1SLOTBT_PHY_BR_2M_3SLOTβBIT 4, EDR 2Mbps 3SLOTBT_PHY_BR_3M_1SLOTβBIT 6, EDR 3Mbps 1SLOTBT_PHY_BR_3M_3SLOTβBIT 7, EDR 3Mbps 3SLOT
(yeah, BIT 2 and BIT 5 justβ¦ aren't a thing here, that's not me being shady, that's literally the list π«Ά)
BT_CODEC (since Linux 5.14) π§
Transport codec offload, and omg the possible values are these two structs:
struct bt_codec {
uint8_t id;
uint16_t cid;
uint16_t vid;
uint8_t data_path_id;
uint8_t num_caps;
struct codec_caps {
uint8_t len;
uint8_t data[];
} caps[];
} __attribute__((packed));
struct bt_codecs {
uint8_t num_codecs;
struct bt_codec codecs[];
} __attribute__((packed));
Bestie, here's an example, no cap π:
char buffer[sizeof(struct bt_codecs) + sizeof(struct bt_codec)];
struct bt_codec *codecs = (void *)buffer;
memset(codecs, 0, sizeof(codecs));
codec->num_codecs = 1;
codecs->codecs[0].id = 0x05;
codecs->codecs[0].data_path_id = 1;
int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_CODEC, codecs,
sizeof(buffer));
if (err == -1) {
perror("setsockopt");
return 1;
}
(yes, that's exactly how it's written, I'm not fixing it, it's iconic as-is π )
β¨ SOCKET OPTIONS (SOL_SOCKET) β¨
The SOL_SOCKET level socket options that tweak generic socket features (SO_SNDBUF, SO_RCVBUF, etc.) keep their usual meaning, okay? Go check socket(7) for the lore.
The SOL_SOCKET level SCO socket options that have Bluetooth-specific handling in the kernel are the ones listed right below. π
SO_TIMESTAMPING, SO_TIMESTAMP, SO_TIMESTAMPNS β°
See https://docs.kernel.org/networking/timestamping.html
For SCO sockets, software RX timestamps are supported. Software TX timestamps (SOF_TIMESTAMPING_TX_SOFTWARE) are supported since Linux 6.15. π
The software RX timestamp is the time when the kernel received the packet from the controller driver.
The SCM_TSTAMP_SND timestamp is emitted when packet is sent to the controller driver.
The SCM_TSTAMP_COMPLETION timestamp is emitted when controller reports the packet completed. Heads up bestie: completion timestamps are only supported on controllers that have SCO flow control. Other TX timestamp types are not supported. π
You can use SIOCETHTOOL to query supported flags.
The timestamps are in CLOCK_REALTIME time.
Example (Enable RX timestamping):
int flags = SOF_TIMESTAMPING_SOFTWARE |
SOF_TIMESTAMPING_RX_SOFTWARE;
setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags));
Example (Read packet and its RX timestamp):
char data_buf[256];
union {
char buf[CMSG_SPACE(sizeof(struct scm_timestamping))];
struct cmsghdr align;
} control;
struct iovec data = {
.iov_base = data_buf,
.iov_len = sizeof(data_buf),
};
struct msghdr msg = {
.msg_iov = &data,
.msg_iovlen = 1,
.msg_control = control.buf,
.msg_controllen = sizeof(control.buf),
};
struct scm_timestamping tss;
res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
if (res < 0)
goto error;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING)
memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss));
}
tstamp_clock_realtime = tss.ts[0];
Example (Enable TX timestamping):
int flags = SOF_TIMESTAMPING_SOFTWARE |
SOF_TIMESTAMPING_TX_SOFTWARE |
SOF_TIMESTAMPING_OPT_ID;
setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags));
Example (Read TX timestamps):
union {
char buf[CMSG_SPACE(sizeof(struct scm_timestamping))];
struct cmsghdr align;
} control;
struct iovec data = {
.iov_base = NULL,
.iov_len = 0
};
struct msghdr msg = {
.msg_iov = &data,
.msg_iovlen = 1,
.msg_control = control.buf,
.msg_controllen = sizeof(control.buf),
};
struct cmsghdr *cmsg;
struct scm_timestamping tss;
struct sock_extended_err serr;
int res;
res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
if (res < 0)
goto error;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING)
memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss));
else if (cmsg->cmsg_level == SOL_BLUETOOTH && cmsg->cmsg_type == BT_SCM_ERROR)
memcpy(&serr, CMSG_DATA(cmsg), sizeof(serr));
}
tstamp_clock_realtime = tss.ts[0];
tstamp_type = serr->ee_info; /* SCM_TSTAMP_SND or SCM_TSTAMP_COMPLETION */
tstamp_seqnum = serr->ee_data;
π¦ IOCTLS π¦
The following ioctls with operation specific for SCO sockets are available. Slay.
SIOCETHTOOL (since Linux 6.16-rc1) π
Supports only the command ETHTOOL_GET_TS_INFO, which may be used to query supported SOF_TIMESTAMPING_* flags. The SOF_TIMESTAMPING_OPT_* flags are always available as applicable. π
Example:
#include <linux/ethtool.h>
#include <linux/sockios.h>
#include <net/if.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
...
struct ifreq ifr = {};
struct ethtool_ts_info cmd = {};
int sk;
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "hci0");
ifr.ifr_data = (void *)&cmd;
cmd.cmd = ETHTOOL_GET_TS_INFO;
sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
if (sk < 0)
goto error;
if (ioctl(sk, SIOCETHTOOL, &ifr))
goto error;
sof_available = cmd.so_timestamping;
π RESOURCES π
π REPORTING BUGS π
linux-bluetooth@vger.kernel.org β please do actually tell them, not just subtweet the kernel π
β SEE ALSO β
socket(7)scotest(1)
π COPYRIGHT π
Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). π«Ά
and that's the whole page bestie!! this little gem is brought to you by BlueZ, dated March 2025, and it's SCO(7) β go forth and timestamp your heart out β¨ππ
π¦ where this page comes from
- centos-stream 9: bluez-libs-devel 5.87+2.git8750129efca8-1.el9 Β· GPLv2+
- centos-stream 9: bluez-libs-devel 5.87+2.git8750129efca8-1.el9 Β· GPLv2+
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms