summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/bluez
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/bluez')
-rw-r--r--src/bluetooth/bluez/bluez_data_p.h71
-rw-r--r--src/bluetooth/bluez/hcimanager.cpp186
-rw-r--r--src/bluetooth/bluez/hcimanager_p.h14
3 files changed, 246 insertions, 25 deletions
diff --git a/src/bluetooth/bluez/bluez_data_p.h b/src/bluetooth/bluez/bluez_data_p.h
index 2fa5ccc2..e8d1ec62 100644
--- a/src/bluetooth/bluez/bluez_data_p.h
+++ b/src/bluetooth/bluez/bluez_data_p.h
@@ -140,22 +140,6 @@ struct sockaddr_rc {
// Bt Low Energy related
-#define bt_get_unaligned(ptr) \
-({ \
- struct __attribute__((packed)) { \
- __typeof__(*(ptr)) __v; \
- } *__p = (__typeof__(__p)) (ptr); \
- __p->__v; \
-})
-
-#define bt_put_unaligned(val, ptr) \
-do { \
- struct __attribute__((packed)) { \
- __typeof__(*(ptr)) __v; \
- } *__p = (__typeof__(__p)) (ptr); \
- __p->__v = (val); \
-} while (0)
-
#if __BYTE_ORDER == __LITTLE_ENDIAN
static inline void btoh128(const quint128 *src, quint128 *dst)
@@ -171,15 +155,7 @@ static inline void ntoh128(const quint128 *src, quint128 *dst)
dst->data[15 - i] = src->data[i];
}
-static inline quint16 bt_get_le16(const void *ptr)
-{
- return bt_get_unaligned((const quint16 *) ptr);
-}
#elif __BYTE_ORDER == __BIG_ENDIAN
-static inline quint16 bt_get_le16(const void *ptr)
-{
- return qbswap(bt_get_unaligned((const quint16 *) ptr));
-}
static inline void btoh128(const quint128 *src, quint128 *dst)
{
@@ -197,6 +173,20 @@ static inline void ntoh128(const quint128 *src, quint128 *dst)
#error "Unknown byte order"
#endif
+static inline quint16 bt_get_le16(const void *ptr)
+{
+ return qFromLittleEndian<quint16>(reinterpret_cast<const uchar *>(ptr));
+}
+
+template<typename T> inline void putBtData(T src, void *dst)
+{
+ qToLittleEndian(src, reinterpret_cast<uchar *>(dst));
+}
+template<> inline void putBtData(quint128 src, void *dst)
+{
+ btoh128(&src, reinterpret_cast<quint128 *>(dst));
+}
+
#define hton128(x, y) ntoh128(x, y)
// HCI related
@@ -209,12 +199,14 @@ static inline void ntoh128(const quint128 *src, quint128 *dst)
#define HCI_FILTER 2
// HCI packet types
+#define HCI_COMMAND_PKT 0x01
#define HCI_EVENT_PKT 0x04
#define HCI_VENDOR_PKT 0xff
#define HCI_FLT_TYPE_BITS 31
#define HCI_FLT_EVENT_BITS 63
+
struct sockaddr_hci {
sa_family_t hci_family;
unsigned short hci_dev;
@@ -339,6 +331,37 @@ typedef struct {
} __attribute__ ((packed)) evt_encrypt_change;
#define EVT_ENCRYPT_CHANGE_SIZE 4
+#define EVT_CMD_COMPLETE 0x0E
+struct evt_cmd_complete {
+ quint8 ncmd;
+ quint16 opcode;
+} __attribute__ ((packed));
+
+struct hci_command_hdr {
+ quint16 opcode; /* OCF & OGF */
+ quint8 plen;
+} __attribute__ ((packed));
+
+enum OpCodeGroupField {
+ OgfLinkControl = 0x8,
+};
+
+enum OpCodeCommandField {
+ OcfLeSetAdvParams = 0x6,
+ OcfLeReadTxPowerLevel = 0x7,
+ OcfLeSetAdvData = 0x8,
+ OcfLeSetScanResponseData = 0x9,
+ OcfLeSetAdvEnable = 0xa,
+ OcfLeClearWhiteList = 0x10,
+ OcfLeAddToWhiteList = 0x11,
+ OcfLeConnectionUpdate = 0x13,
+};
+
+/* Command opcode pack/unpack */
+#define opCodePack(ogf, ocf) (quint16(((ocf) & 0x03ff)|((ogf) << 10)))
+#define ogfFromOpCode(op) ((op) >> 10)
+#define ocfFromOpCode(op) ((op) & 0x03ff)
+
QT_END_NAMESPACE
#endif // BLUEZ_DATA_P_H
diff --git a/src/bluetooth/bluez/hcimanager.cpp b/src/bluetooth/bluez/hcimanager.cpp
index 8f5f9dd9..123a16ab 100644
--- a/src/bluetooth/bluez/hcimanager.cpp
+++ b/src/bluetooth/bluez/hcimanager.cpp
@@ -41,13 +41,16 @@
#include "hcimanager_p.h"
#include "qbluetoothsocket_p.h"
+#include "qlowenergyconnectionparameters.h"
-#include <QtCore/QLoggingCategory>
+#include <QtCore/qloggingcategory.h>
+#include <cstring>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
+#include <sys/uio.h>
#include <unistd.h>
#define HCIGETCONNLIST _IOR('H', 212, int)
@@ -180,6 +183,36 @@ bool HciManager::monitorEvent(HciManager::HciEvent event)
return true;
}
+bool HciManager::sendCommand(OpCodeGroupField ogf, OpCodeCommandField ocf, const QByteArray &parameters)
+{
+ qCDebug(QT_BT_BLUEZ) << "sending command; ogf:" << ogf << "ocf:" << ocf;
+ quint8 packetType = HCI_COMMAND_PKT;
+ hci_command_hdr command = {
+ opCodePack(ogf, ocf),
+ static_cast<uint8_t>(parameters.count())
+ };
+ static_assert(sizeof command == 3, "unexpected struct size");
+ struct iovec iv[3];
+ iv[0].iov_base = &packetType;
+ iv[0].iov_len = 1;
+ iv[1].iov_base = &command;
+ iv[1].iov_len = sizeof command;
+ int ivn = 2;
+ if (!parameters.isEmpty()) {
+ iv[2].iov_base = const_cast<char *>(parameters.constData()); // const_cast is safe, since iov_base will not get modified.
+ iv[2].iov_len = parameters.count();
+ ++ivn;
+ }
+ while (writev(hciSocket, iv, ivn) < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ qCDebug(QT_BT_BLUEZ()) << "hci command failure:" << strerror(errno);
+ return false;
+ }
+ qCDebug(QT_BT_BLUEZ) << "command sent successfully";
+ return true;
+}
+
/*
* Unsubscribe from all events
*/
@@ -232,6 +265,108 @@ QBluetoothAddress HciManager::addressForConnectionHandle(quint16 handle) const
return QBluetoothAddress();
}
+quint16 forceIntervalIntoRange(double connectionInterval)
+{
+ return qMin<double>(qMax<double>(7.5, connectionInterval), 4000) / 1.25;
+}
+
+struct ConnectionUpdateData {
+ quint16 minInterval;
+ quint16 maxInterval;
+ quint16 slaveLatency;
+ quint16 timeout;
+};
+ConnectionUpdateData connectionUpdateData(const QLowEnergyConnectionParameters &params)
+{
+ ConnectionUpdateData data;
+ const quint16 minInterval = forceIntervalIntoRange(params.minimumInterval());
+ const quint16 maxInterval = forceIntervalIntoRange(params.maximumInterval());
+ data.minInterval = qToLittleEndian(minInterval);
+ data.maxInterval = qToLittleEndian(maxInterval);
+ const quint16 latency = qMax<quint16>(0, qMin<quint16>(params.latency(), 499));
+ data.slaveLatency = qToLittleEndian(latency);
+ const quint16 timeout
+ = qMax<quint16>(100, qMin<quint16>(32000, params.supervisionTimeout())) / 10;
+ data.timeout = qToLittleEndian(timeout);
+ return data;
+}
+
+bool HciManager::sendConnectionUpdateCommand(quint16 handle,
+ const QLowEnergyConnectionParameters &params)
+{
+ struct CommandParams {
+ quint16 handle;
+ ConnectionUpdateData data;
+ quint16 minCeLength;
+ quint16 maxCeLength;
+ } commandParams;
+ commandParams.handle = qToLittleEndian(handle);
+ commandParams.data = connectionUpdateData(params);
+ commandParams.minCeLength = 0;
+ commandParams.maxCeLength = qToLittleEndian(quint16(0xffff));
+ const QByteArray data = QByteArray::fromRawData(reinterpret_cast<char *>(&commandParams),
+ sizeof commandParams);
+ return sendCommand(OgfLinkControl, OcfLeConnectionUpdate, data);
+}
+
+bool HciManager::sendConnectionParameterUpdateRequest(quint16 handle,
+ const QLowEnergyConnectionParameters &params)
+{
+ ConnectionUpdateData connUpdateData = connectionUpdateData(params);
+
+ // Vol 3, part A, 4
+ struct SignalingPacket {
+ quint8 code;
+ quint8 identifier;
+ quint16 length;
+ } signalingPacket;
+ signalingPacket.code = 0x12;
+ signalingPacket.identifier = ++sigPacketIdentifier;
+ const quint16 sigPacketLen = sizeof connUpdateData;
+ signalingPacket.length = qToLittleEndian(sigPacketLen);
+
+ struct L2CapHeader {
+ quint16 length;
+ quint16 channelId;
+ } l2CapHeader;
+ const quint16 l2CapHeaderLen = sizeof signalingPacket + sigPacketLen;
+ l2CapHeader.length = qToLittleEndian(l2CapHeaderLen);
+ l2CapHeader.channelId = qToLittleEndian(quint16(5));
+
+ // Vol 2, part E, 5.4.2
+ struct AclData {
+ quint16 handle: 12;
+ quint16 pbFlag: 2;
+ quint16 bcFlag: 2;
+ quint16 dataLen;
+ } aclData;
+ aclData.handle = qToLittleEndian(handle); // Works because the next two values are zero.
+ aclData.pbFlag = 0;
+ aclData.bcFlag = 0;
+ aclData.dataLen = qToLittleEndian(quint16(sizeof l2CapHeader + l2CapHeaderLen));
+
+ struct iovec iv[5];
+ quint8 packetType = 2;
+ iv[0].iov_base = &packetType;
+ iv[0].iov_len = 1;
+ iv[1].iov_base = &aclData;
+ iv[1].iov_len = sizeof aclData;
+ iv[2].iov_base = &l2CapHeader;
+ iv[2].iov_len = sizeof l2CapHeader;
+ iv[3].iov_base = &signalingPacket;
+ iv[3].iov_len = sizeof signalingPacket;
+ iv[4].iov_base = &connUpdateData;
+ iv[4].iov_len = sizeof connUpdateData;
+ while (writev(hciSocket, iv, sizeof iv / sizeof *iv) < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ qCDebug(QT_BT_BLUEZ()) << "failure writing HCI ACL packet:" << strerror(errno);
+ return false;
+ }
+ qCDebug(QT_BT_BLUEZ) << "Connection Update Request packet sent successfully";
+ return true;
+}
+
/*!
* Process all incoming HCI events. Function cannot process anything else but events.
*/
@@ -281,10 +416,59 @@ void HciManager::_q_readNotify()
emit encryptionChangedEvent(remoteDevice, event->status == 0);
}
break;
+ case EVT_CMD_COMPLETE: {
+ auto * const event = reinterpret_cast<const evt_cmd_complete *>(data);
+ static_assert(sizeof *event == 3, "unexpected struct size");
+
+ // There is always a status byte right after the generic structure.
+ Q_ASSERT(size > static_cast<int>(sizeof *event));
+ const quint8 status = data[sizeof *event];
+ const auto additionalData = QByteArray(reinterpret_cast<const char *>(data)
+ + sizeof *event + 1, size - sizeof *event - 1);
+ emit commandCompleted(event->opcode, status, additionalData);
+ }
+ break;
+ case LeMetaEvent:
+ handleLeMetaEvent(data);
+ break;
default:
break;
}
}
+void HciManager::handleLeMetaEvent(const quint8 *data)
+{
+ // Spec v4.2, Vol 2, part E, 7.7.65ff
+ switch (*data) {
+ case 0x1: {
+ const quint16 handle = bt_get_le16(data + 2);
+ emit connectionComplete(handle);
+ break;
+ }
+ case 0x3: {
+ // TODO: From little endian!
+ struct ConnectionUpdateData {
+ quint8 status;
+ quint16 handle;
+ quint16 interval;
+ quint16 latency;
+ quint16 timeout;
+ } __attribute((packed));
+ const auto * const updateData
+ = reinterpret_cast<const ConnectionUpdateData *>(data + 1);
+ if (updateData->status == 0) {
+ QLowEnergyConnectionParameters params;
+ const double interval = qFromLittleEndian(updateData->interval) * 1.25;
+ params.setIntervalRange(interval, interval);
+ params.setLatency(qFromLittleEndian(updateData->latency));
+ params.setSupervisionTimeout(qFromLittleEndian(updateData->timeout) * 10);
+ emit connectionUpdate(qFromLittleEndian(updateData->handle), params);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
QT_END_NAMESPACE
diff --git a/src/bluetooth/bluez/hcimanager_p.h b/src/bluetooth/bluez/hcimanager_p.h
index f90d0bfb..b2edb15b 100644
--- a/src/bluetooth/bluez/hcimanager_p.h
+++ b/src/bluetooth/bluez/hcimanager_p.h
@@ -59,12 +59,16 @@
QT_BEGIN_NAMESPACE
+class QLowEnergyConnectionParameters;
+
class HciManager : public QObject
{
Q_OBJECT
public:
enum HciEvent {
EncryptChangeEvent = EVT_ENCRYPT_CHANGE,
+ CommandCompleteEvent = EVT_CMD_COMPLETE,
+ LeMetaEvent = 0x3e,
};
explicit HciManager(const QBluetoothAddress &deviceAdapter, QObject *parent = 0);
@@ -72,21 +76,31 @@ public:
bool isValid() const;
bool monitorEvent(HciManager::HciEvent event);
+ bool sendCommand(OpCodeGroupField ogf, OpCodeCommandField ocf, const QByteArray &parameters);
+
void stopEvents();
QBluetoothAddress addressForConnectionHandle(quint16 handle) const;
+ bool sendConnectionUpdateCommand(quint16 handle, const QLowEnergyConnectionParameters &params);
+ bool sendConnectionParameterUpdateRequest(quint16 handle,
+ const QLowEnergyConnectionParameters &params);
signals:
void encryptionChangedEvent(const QBluetoothAddress &address, bool wasSuccess);
+ void commandCompleted(quint16 opCode, quint8 status, const QByteArray &data);
+ void connectionComplete(quint16 handle);
+ void connectionUpdate(quint16 handle, const QLowEnergyConnectionParameters &parameters);
private slots:
void _q_readNotify();
private:
int hciForAddress(const QBluetoothAddress &deviceAdapter);
+ void handleLeMetaEvent(const quint8 *data);
int hciSocket;
int hciDev;
+ quint8 sigPacketIdentifier = 0;
QSocketNotifier *notifier;
QSet<HciManager::HciEvent> runningEvents;
};