Skip to content

Commit aed90dd

Browse files
committed
bluetooth: implement HOGP shorter connection intervals feature
Signed-off-by: Benedek Kupper <kupper.benedek@gmail.com>
1 parent ea5a43b commit aed90dd

10 files changed

Lines changed: 658 additions & 45 deletions

File tree

c2usb/bluetooth/Kconfig

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,17 @@ source "subsys/logging/Kconfig.template.log_config"
1212
config C2USB_HOGP_POWER_EVENT
1313
bool "Enable HOGP control point power event callback"
1414

15-
config C2USB_HOGP_BT_DISCONN_CB
16-
bool "HOGP registers its own Bluetooth disconnection callback through BT_CONN_CB_DEFINE"
17-
default y
18-
help
19-
If enabled, HOGP registers its own Bluetooth disconnection callback through BT_CONN_CB_DEFINE.
20-
If disabled, the application must register its own Bluetooth disconnection callback and call
21-
bluetooth::hid_over_gatt::service::disconnect_callback() from it.
15+
config C2USB_HOGP_SCI
16+
bool "Enable HID Shorter Connection Intervals (SCI) support"
17+
depends on BT_SHORTER_CONNECTION_INTERVALS
18+
19+
if C2USB_HOGP_SCI
20+
21+
config C2USB_HOGP_MAX_CONN_INTERVAL_GROUPS
22+
int "Maximum number of connection interval groups"
23+
default 4
24+
range 0 41
25+
26+
endif # C2USB_HOGP_SCI
2227

2328
endif # C2USB_HOGP

c2usb/bluetooth/hid_over_gatt.cpp

Lines changed: 169 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
#include <hid/app/keyboard.hpp>
55
#include <hid/app/mouse.hpp>
66
#include <zephyr/logging/log.h>
7+
#if CONFIG_C2USB_HOGP_SCI
8+
#include <zephyr/bluetooth/hci.h>
9+
#include <zephyr/bluetooth/hci_types.h>
10+
#endif
711

812
LOG_MODULE_REGISTER(hogp, CONFIG_C2USB_HOGP_LOG_LEVEL);
913

@@ -79,7 +83,7 @@ ssize_t service::control_point_request(::bt_conn* conn, const ::bt_gatt_attr* at
7983
{
8084
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
8185
}
82-
if (len > sizeof(uint8_t))
86+
if (len != sizeof(uint8_t))
8387
{
8488
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
8589
}
@@ -97,6 +101,16 @@ ssize_t service::control_point_request(::bt_conn* conn, const ::bt_gatt_attr* at
97101
}
98102
#endif
99103
return len;
104+
#if defined(CONFIG_C2USB_HOGP_SCI)
105+
case uint8_t(sci_mode::DEFAULT):
106+
case uint8_t(sci_mode::FAST):
107+
case uint8_t(sci_mode::FULL_RANGE):
108+
case uint8_t(sci_mode::LOW_POWER):
109+
return static_cast<service*>(attr->user_data)
110+
->set_sci_mode(conn, static_cast<sci_mode>(cmd))
111+
? len
112+
: BT_GATT_ERR(BT_ATT_ERR_NOT_SUPPORTED);
113+
#endif
100114
default:
101115
return BT_GATT_ERR(BT_ATT_ERR_NOT_SUPPORTED);
102116
}
@@ -397,6 +411,153 @@ ssize_t service::ccc_cfg_write(::bt_conn* conn, const gatt::attribute* attr, gat
397411
return (session.protocol() == prot) ? sizeof(flags) : BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
398412
}
399413

414+
#if CONFIG_C2USB_HOGP_SCI
415+
ssize_t service::get_sci_information(::bt_conn* conn, const ::bt_gatt_attr* attr, void* buf,
416+
uint16_t len, uint16_t offset)
417+
{
418+
if (sci_attributes_.min_supported_conn_interval == 0)
419+
{
420+
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
421+
}
422+
return bt_gatt_attr_read(conn, attr, buf, len, offset,
423+
reinterpret_cast<const uint8_t*>(&sci_attributes_),
424+
offsetof(decltype(sci_attributes_), groups) +
425+
sci_attributes_.num_groups * sizeof(sci_attributes_.groups[0]));
426+
}
427+
428+
ssize_t service::get_sci_mode(::bt_conn* conn, const ::bt_gatt_attr* attr, void* buf, uint16_t len,
429+
uint16_t offset)
430+
{
431+
auto* self = static_cast<service*>(attr->user_data);
432+
auto it = std::ranges::find_if(self->sessions_, [&](const auto& s) { return s.conn == conn; });
433+
if (it == self->sessions_.end())
434+
{
435+
uint8_t mode = uint8_t(sci_mode::NONE);
436+
return bt_gatt_attr_read(conn, attr, buf, len, offset, &mode, sizeof(mode));
437+
}
438+
return bt_gatt_attr_read(conn, attr, buf, len, offset,
439+
reinterpret_cast<const uint8_t*>(&it->active_sci_mode_),
440+
sizeof(it->active_sci_mode_));
441+
}
442+
443+
bool service::set_sci_mode(::bt_conn* conn, sci_mode mode)
444+
{
445+
if (sci_attributes_.min_supported_conn_interval == 0)
446+
{
447+
return false;
448+
}
449+
auto it = std::ranges::find_if(sessions_, [&](const auto& s) { return s.conn == conn; });
450+
if (it == sessions_.end())
451+
{
452+
// no session yet, use a slot for this connection, without a session
453+
auto it = std::ranges::find_if(sessions_, [&](const auto& s) { return s.conn == nullptr; });
454+
assert((it != sessions_.end()) and "no free conn_session slots");
455+
it->conn = conn;
456+
}
457+
458+
auto params = std::find_if(sci_mode_params_.begin(), sci_mode_params_.end(),
459+
[mode](const auto& p) { return p.mode == mode; });
460+
if (params == sci_mode_params_.end())
461+
{
462+
assert((mode == sci_mode::LOW_POWER) and "Missing mandatory SCI mode parameters");
463+
return false;
464+
}
465+
466+
auto& sci_params = *params;
467+
if (auto ret = bt_conn_le_conn_rate_request(conn, &sci_params); ret != 0)
468+
{
469+
LOG_ERR("Failed to request connection rate change: %d", ret);
470+
return false;
471+
}
472+
473+
it->pending_sci_mode_ = mode;
474+
return true;
475+
}
476+
477+
void service::connect_callback(bt_conn* conn, uint8_t err)
478+
{
479+
if (sci_attributes_.min_supported_conn_interval == 0)
480+
{
481+
// first time initialization, fetch and store the SCI information for all subsequent use
482+
const size_t offset =
483+
offsetof(::bt_hci_op_le_read_min_supported_conn_interval, min_supported_conn_interval);
484+
struct net_buf* rsp;
485+
if (auto ret =
486+
bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_MIN_SUPPORTED_CONN_INTERVAL, nullptr, &rsp);
487+
(ret != 0) or (rsp->len < offset + sizeof(sci_attributes<0>)))
488+
{
489+
LOG_ERR("Failed to read min supported connection interval: %d", ret);
490+
return;
491+
}
492+
493+
size_t size = std::min<size_t>(sizeof(sci_attributes_), rsp->len - offset);
494+
std::copy_n(rsp->data + offset, size,
495+
reinterpret_cast<uint8_t*>(&sci_attributes_.min_supported_conn_interval));
496+
497+
size -= offsetof(decltype(sci_attributes_), groups);
498+
if (sci_attributes_.num_groups > sci_attributes_.groups.size())
499+
{
500+
LOG_WRN("Supported connection interval groups exceeds storage: %d (max %d)",
501+
sci_attributes_.num_groups, sci_attributes_.groups.size());
502+
sci_attributes_.num_groups = sci_attributes_.groups.size();
503+
}
504+
505+
net_buf_unref(rsp);
506+
}
507+
}
508+
509+
void service::connection_rate_callback(::bt_conn* conn, uint8_t status,
510+
const ::bt_conn_le_conn_rate_changed* params)
511+
{
512+
if (status != BT_HCI_ERR_SUCCESS)
513+
{
514+
return;
515+
}
516+
auto change_params = connection_rate_params{conn, params};
517+
for_each<const connection_rate_params, &service::connection_rate_changed>(&change_params);
518+
}
519+
520+
void service::connection_rate_changed(const connection_rate_params* p)
521+
{
522+
auto it = std::ranges::find_if(sessions_, [&](const auto& s) { return s.conn == p->conn; });
523+
if (it == sessions_.end())
524+
{
525+
return;
526+
}
527+
auto new_mode = it->pending_sci_mode_;
528+
it->pending_sci_mode_ = sci_mode::NONE;
529+
if (new_mode != sci_mode::NONE)
530+
{
531+
auto mode_params = std::find_if(sci_mode_params_.begin(), sci_mode_params_.end(),
532+
[new_mode](const auto& p) { return p.mode == new_mode; });
533+
if ((mode_params == sci_mode_params_.end()) or !mode_params->match(*p->params))
534+
{
535+
LOG_WRN("Connection %p rate change does not match requested SCI mode %u", p->conn,
536+
uint8_t(new_mode));
537+
new_mode = sci_mode::NONE;
538+
}
539+
}
540+
if (new_mode == sci_mode::NONE)
541+
{
542+
// no pending SCI mode change request, find a matching mode
543+
auto matching_params =
544+
std::find_if(sci_mode_params_.begin(), sci_mode_params_.end(),
545+
[in = p->params](const auto& p) { return p.match(*in); });
546+
new_mode =
547+
(matching_params != sci_mode_params_.end()) ? matching_params->mode : sci_mode::NONE;
548+
}
549+
550+
if (new_mode != it->active_sci_mode_)
551+
{
552+
it->active_sci_mode_ = new_mode;
553+
LOG_INF("Connection %p SCI mode changed to %u", p->conn, uint8_t(new_mode));
554+
sci_mode_attr()->notify(
555+
std::span<const uint8_t>(reinterpret_cast<const uint8_t*>(&new_mode), sizeof(new_mode)),
556+
p->conn);
557+
}
558+
}
559+
#endif
560+
400561
void service::disconnected(::bt_conn* conn)
401562
{
402563
auto it = std::ranges::find_if(sessions_, [&](const auto& s) { return s.conn == conn; });
@@ -409,13 +570,17 @@ void service::disconnected(::bt_conn* conn)
409570

410571
void service::disconnect_callback(::bt_conn* conn, uint8_t reason)
411572
{
412-
for_each<::bt_conn*, &service::disconnected>(conn);
573+
for_each<::bt_conn, &service::disconnected>(conn);
413574
}
414575

415576
} // namespace bluetooth::hid_over_gatt
416577

417-
#if CONFIG_C2USB_HOGP_BT_DISCONN_CB
418578
BT_CONN_CB_DEFINE(hid_over_gatt_conn_callbacks) = {
579+
#if CONFIG_C2USB_HOGP_SCI
580+
.connected = &bluetooth::hid_over_gatt::service::connect_callback,
581+
#endif
419582
.disconnected = &bluetooth::hid_over_gatt::service::disconnect_callback,
420-
};
583+
#if CONFIG_C2USB_HOGP_SCI
584+
.conn_rate_changed = &bluetooth::hid_over_gatt::service::connection_rate_callback,
421585
#endif
586+
};

0 commit comments

Comments
 (0)