/* * Copyright (c) 2022 Codecoup * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include "../bluetooth/host/conn_internal.h" #include "../bluetooth/host/hci_core.h" #include "audio_internal.h" #include "has_internal.h" #include LOG_MODULE_REGISTER(bt_has, CONFIG_BT_HAS_LOG_LEVEL); /* The service allows operations with paired devices only. * For now, the context is kept for connected devices only, thus the number of contexts is * equal to maximum number of simultaneous connections to paired devices. */ #define BT_HAS_MAX_CONN MIN(CONFIG_BT_MAX_CONN, CONFIG_BT_MAX_PAIRED) static struct bt_has has; #if defined(CONFIG_BT_HAS_PRESET_SUPPORT) static ssize_t write_control_point(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *data, uint16_t len, uint16_t offset, uint8_t flags); static ssize_t read_active_preset_index(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { LOG_DBG("conn %p attr %p offset %d", (void *)conn, attr, offset); if (offset > sizeof(has.active_index)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } return bt_gatt_attr_read(conn, attr, buf, len, offset, &has.active_index, sizeof(has.active_index)); } static void ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("attr %p value 0x%04x", attr, value); } #endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ static ssize_t read_features(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { LOG_DBG("conn %p attr %p offset %d", (void *)conn, attr, offset); if (offset > sizeof(has.features)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } return bt_gatt_attr_read(conn, attr, buf, len, offset, &has.features, sizeof(has.features)); } /* Hearing Access Service GATT Attributes */ static struct bt_gatt_attr has_attrs[] = { BT_GATT_PRIMARY_SERVICE(BT_UUID_HAS), BT_AUDIO_CHRC(BT_UUID_HAS_HEARING_AID_FEATURES, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, read_features, NULL, NULL), #if defined(CONFIG_BT_HAS_PRESET_SUPPORT) BT_AUDIO_CHRC(BT_UUID_HAS_PRESET_CONTROL_POINT, #if defined(CONFIG_BT_EATT) BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_NOTIFY, #else BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE, #endif /* CONFIG_BT_EATT */ BT_GATT_PERM_WRITE_ENCRYPT, NULL, write_control_point, NULL), BT_AUDIO_CCC(ccc_cfg_changed), BT_AUDIO_CHRC(BT_UUID_HAS_ACTIVE_PRESET_INDEX, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, read_active_preset_index, NULL, NULL), BT_AUDIO_CCC(ccc_cfg_changed), #endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ }; static struct bt_gatt_service has_svc; #if defined(CONFIG_BT_HAS_PRESET_SUPPORT) #define PRESET_CONTROL_POINT_ATTR &has_attrs[4] #define ACTIVE_PRESET_INDEX_ATTR &has_attrs[7] static struct has_client { struct bt_conn *conn; union { struct bt_gatt_indicate_params ind; #if defined(CONFIG_BT_EATT) struct bt_gatt_notify_params ntf; #endif /* CONFIG_BT_EATT */ } params; struct { bool is_pending; uint8_t preset_changed_index_next; } ntf_bonded; struct bt_has_cp_read_presets_req read_presets_req; struct k_work control_point_work; struct k_work_sync control_point_work_sync; } has_client_list[BT_HAS_MAX_CONN]; /* HAS internal preset representation */ static struct has_preset { uint8_t index; enum bt_has_properties properties; #if defined(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC) char name[BT_HAS_PRESET_NAME_MAX + 1]; /* +1 byte for NULL-terminator */ #else const char *name; #endif /* CONFIG_BT_HAS_PRESET_NAME_DYNAMIC */ const struct bt_has_preset_ops *ops; } has_preset_list[CONFIG_BT_HAS_PRESET_COUNT]; /* Number of registered presets */ static uint8_t has_preset_num; /* Active preset notification work */ static struct k_work active_preset_work; static void process_control_point_work(struct k_work *work); static struct has_client *client_get_or_new(struct bt_conn *conn) { struct has_client *client = NULL; for (size_t i = 0; i < ARRAY_SIZE(has_client_list); i++) { if (conn == has_client_list[i].conn) { return &has_client_list[i]; } /* first free slot */ if (!client && !has_client_list[i].conn) { client = &has_client_list[i]; } } __ASSERT(client, "failed to get client for conn %p", (void *)conn); client->conn = bt_conn_ref(conn); k_work_init(&client->control_point_work, process_control_point_work); return client; } static bool read_presets_req_is_pending(struct has_client *client) { return client->read_presets_req.num_presets > 0; } static void read_presets_req_free(struct has_client *client) { client->read_presets_req.num_presets = 0; } static void client_free(struct has_client *client) { (void)k_work_cancel(&client->control_point_work); read_presets_req_free(client); client->ntf_bonded.is_pending = false; bt_conn_unref(client->conn); client->conn = NULL; } static struct has_client *client_get(struct bt_conn *conn) { for (size_t i = 0; i < ARRAY_SIZE(has_client_list); i++) { if (conn == has_client_list[i].conn) { return &has_client_list[i]; } } return NULL; } static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err) { struct has_client *client; LOG_DBG("conn %p level %d err %d", (void *)conn, level, err); if (err != BT_SECURITY_ERR_SUCCESS || !bt_addr_le_is_bonded(conn->id, &conn->le.dst)) { return; } client = client_get_or_new(conn); if (unlikely(!client)) { LOG_ERR("Failed to allocate client"); return; } /* Notify after reconnection */ if (client->ntf_bonded.is_pending) { /* Emit active preset notification */ k_work_submit(&active_preset_work); /* Emit preset changed notifications */ k_work_submit(&client->control_point_work); } } static void connected(struct bt_conn *conn, uint8_t err) { struct has_client *client; LOG_DBG("conn %p err %d", conn, err); if (err != 0 || !bt_addr_le_is_bonded(conn->id, &conn->le.dst)) { return; } client = client_get_or_new(conn); if (unlikely(!client)) { LOG_ERR("Failed to allocate client"); return; } /* Mark preset changed operation as pending */ client->ntf_bonded.is_pending = true; client->ntf_bonded.preset_changed_index_next = BT_HAS_PRESET_INDEX_FIRST; } static void disconnected(struct bt_conn *conn, uint8_t reason) { struct has_client *client; LOG_DBG("conn %p reason %d", (void *)conn, reason); client = client_get(conn); if (client) { client_free(client); } } BT_CONN_CB_DEFINE(conn_cb) = { .connected = connected, .disconnected = disconnected, .security_changed = security_changed, }; typedef uint8_t (*preset_func_t)(const struct has_preset *preset, void *user_data); static void preset_foreach(uint8_t start_index, uint8_t end_index, preset_func_t func, void *user_data) { for (size_t i = 0; i < ARRAY_SIZE(has_preset_list); i++) { const struct has_preset *preset = &has_preset_list[i]; if (preset->index < start_index) { continue; } if (preset->index > end_index) { return; } if (func(preset, user_data) == BT_HAS_PRESET_ITER_STOP) { return; } } } static uint8_t preset_found(const struct has_preset *preset, void *user_data) { const struct has_preset **found = user_data; *found = preset; return BT_HAS_PRESET_ITER_STOP; } static int preset_index_compare(const void *p1, const void *p2) { const struct has_preset *preset_1 = p1; const struct has_preset *preset_2 = p2; if (preset_1->index == BT_HAS_PRESET_INDEX_NONE) { return 1; } if (preset_2->index == BT_HAS_PRESET_INDEX_NONE) { return -1; } return preset_1->index - preset_2->index; } static struct has_preset *preset_alloc(uint8_t index, enum bt_has_properties properties, const char *name, const struct bt_has_preset_ops *ops) { struct has_preset *preset = NULL; if (has_preset_num < ARRAY_SIZE(has_preset_list)) { preset = &has_preset_list[has_preset_num]; preset->index = index; preset->properties = properties; #if defined(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC) utf8_lcpy(preset->name, name, ARRAY_SIZE(preset->name)); #else preset->name = name; #endif /* CONFIG_BT_HAS_PRESET_NAME_DYNAMIC */ preset->ops = ops; has_preset_num++; /* sort the presets in index ascending order */ qsort(has_preset_list, has_preset_num, sizeof(*preset), preset_index_compare); } return preset; } static void preset_free(struct has_preset *preset) { preset->index = BT_HAS_PRESET_INDEX_NONE; /* sort the presets in index ascending order */ if (has_preset_num > 1) { qsort(has_preset_list, has_preset_num, sizeof(*preset), preset_index_compare); } has_preset_num--; } static void control_point_ntf_complete(struct bt_conn *conn, void *user_data) { struct has_client *client = client_get(conn); LOG_DBG("conn %p", (void *)conn); /* Resubmit if needed */ if (client != NULL && (read_presets_req_is_pending(client) || client->ntf_bonded.is_pending)) { k_work_submit(&client->control_point_work); } } static void control_point_ind_complete(struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err) { if (err) { /* TODO: Handle error somehow */ LOG_ERR("conn %p err 0x%02x", (void *)conn, err); } control_point_ntf_complete(conn, NULL); } static int control_point_send(struct has_client *client, struct net_buf_simple *buf) { #if defined(CONFIG_BT_EATT) if (bt_eatt_count(client->conn) > 0 && bt_gatt_is_subscribed(client->conn, PRESET_CONTROL_POINT_ATTR, BT_GATT_CCC_NOTIFY)) { client->params.ntf.attr = PRESET_CONTROL_POINT_ATTR; client->params.ntf.func = control_point_ntf_complete; client->params.ntf.data = buf->data; client->params.ntf.len = buf->len; return bt_gatt_notify_cb(client->conn, &client->params.ntf); } #endif /* CONFIG_BT_EATT */ if (bt_gatt_is_subscribed(client->conn, PRESET_CONTROL_POINT_ATTR, BT_GATT_CCC_INDICATE)) { client->params.ind.attr = PRESET_CONTROL_POINT_ATTR; client->params.ind.func = control_point_ind_complete; client->params.ind.destroy = NULL; client->params.ind.data = buf->data; client->params.ind.len = buf->len; return bt_gatt_indicate(client->conn, &client->params.ind); } return -ECANCELED; } static int control_point_send_all(struct net_buf_simple *buf) { int result = 0; for (size_t i = 0; i < ARRAY_SIZE(has_client_list); i++) { struct has_client *client = &has_client_list[i]; int err; if (!client->conn) { continue; } if (!bt_gatt_is_subscribed(client->conn, PRESET_CONTROL_POINT_ATTR, BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE)) { continue; } err = control_point_send(client, buf); if (err) { result = err; /* continue anyway */ } } return result; } static int bt_has_cp_read_preset_rsp(struct has_client *client, const struct has_preset *preset, bool is_last) { struct bt_has_cp_hdr *hdr; struct bt_has_cp_read_preset_rsp *rsp; NET_BUF_SIMPLE_DEFINE(buf, sizeof(*hdr) + sizeof(*rsp) + BT_HAS_PRESET_NAME_MAX); LOG_DBG("conn %p preset %p is_last 0x%02x", (void *)client->conn, preset, is_last); hdr = net_buf_simple_add(&buf, sizeof(*hdr)); hdr->opcode = BT_HAS_OP_READ_PRESET_RSP; rsp = net_buf_simple_add(&buf, sizeof(*rsp)); rsp->is_last = is_last ? 0x01 : 0x00; rsp->index = preset->index; rsp->properties = preset->properties; net_buf_simple_add_mem(&buf, preset->name, strlen(preset->name)); return control_point_send(client, &buf); } static uint8_t get_prev_preset_index(const struct has_preset *preset) { const struct has_preset *prev = NULL; for (size_t i = 0; i < ARRAY_SIZE(has_preset_list); i++) { const struct has_preset *tmp = &has_preset_list[i]; if (tmp->index == BT_HAS_PRESET_INDEX_NONE || tmp == preset) { break; } prev = tmp; } return prev ? prev->index : BT_HAS_PRESET_INDEX_NONE; } static void preset_changed_prepare(struct net_buf_simple *buf, uint8_t change_id, uint8_t is_last) { struct bt_has_cp_hdr *hdr; struct bt_has_cp_preset_changed *preset_changed; hdr = net_buf_simple_add(buf, sizeof(*hdr)); hdr->opcode = BT_HAS_OP_PRESET_CHANGED; preset_changed = net_buf_simple_add(buf, sizeof(*preset_changed)); preset_changed->change_id = change_id; preset_changed->is_last = is_last; } static int bt_has_cp_generic_update(struct has_client *client, const struct has_preset *preset, uint8_t is_last) { struct bt_has_cp_generic_update *generic_update; NET_BUF_SIMPLE_DEFINE(buf, sizeof(struct bt_has_cp_hdr) + sizeof(struct bt_has_cp_preset_changed) + sizeof(struct bt_has_cp_generic_update) + BT_HAS_PRESET_NAME_MAX); preset_changed_prepare(&buf, BT_HAS_CHANGE_ID_GENERIC_UPDATE, is_last); generic_update = net_buf_simple_add(&buf, sizeof(*generic_update)); generic_update->prev_index = get_prev_preset_index(preset); generic_update->index = preset->index; generic_update->properties = preset->properties; net_buf_simple_add_mem(&buf, preset->name, strlen(preset->name)); if (client) { return control_point_send(client, &buf); } else { return control_point_send_all(&buf); } } static void process_control_point_work(struct k_work *work) { struct has_client *client = CONTAINER_OF(work, struct has_client, control_point_work); int err; if (!client->conn) { return; } if (read_presets_req_is_pending(client)) { const struct has_preset *preset = NULL; bool is_last = true; preset_foreach(client->read_presets_req.start_index, BT_HAS_PRESET_INDEX_LAST, preset_found, &preset); if (unlikely(preset == NULL)) { (void)bt_has_cp_read_preset_rsp(client, NULL, 0x01); return; } if (client->read_presets_req.num_presets > 1) { const struct has_preset *next = NULL; preset_foreach(preset->index + 1, BT_HAS_PRESET_INDEX_LAST, preset_found, &next); is_last = next == NULL; } err = bt_has_cp_read_preset_rsp(client, preset, is_last); if (err) { LOG_ERR("bt_has_cp_read_preset_rsp failed (err %d)", err); } if (err || is_last) { read_presets_req_free(client); } else { client->read_presets_req.start_index = preset->index + 1; client->read_presets_req.num_presets--; } } else if (client->ntf_bonded.is_pending) { const struct has_preset *preset = NULL; const struct has_preset *next = NULL; bool is_last = true; preset_foreach(client->ntf_bonded.preset_changed_index_next, BT_HAS_PRESET_INDEX_LAST, preset_found, &preset); if (preset == NULL) { return; } preset_foreach(preset->index + 1, BT_HAS_PRESET_INDEX_LAST, preset_found, &next); is_last = next == NULL; err = bt_has_cp_generic_update(client, preset, is_last); if (err) { LOG_ERR("bt_has_cp_read_preset_rsp failed (err %d)", err); } if (err || is_last) { client->ntf_bonded.is_pending = false; } else { client->ntf_bonded.preset_changed_index_next = preset->index + 1; } } } static uint8_t handle_read_preset_req(struct bt_conn *conn, struct net_buf_simple *buf) { const struct bt_has_cp_read_presets_req *req; const struct has_preset *preset = NULL; struct has_client *client; if (buf->len < sizeof(*req)) { return BT_HAS_ERR_INVALID_PARAM_LEN; } /* As per HAS_d1.0r00 Client Characteristic Configuration Descriptor Improperly Configured * shall be returned if client writes Read Presets Request but is not registered for * indications. */ if (!bt_gatt_is_subscribed(conn, PRESET_CONTROL_POINT_ATTR, BT_GATT_CCC_INDICATE)) { return BT_ATT_ERR_CCC_IMPROPER_CONF; } client = client_get(conn); if (!client) { return BT_ATT_ERR_UNLIKELY; } req = net_buf_simple_pull_mem(buf, sizeof(*req)); LOG_DBG("start_index %d num_presets %d", req->start_index, req->num_presets); /* Abort if there is no preset in requested index range */ preset_foreach(req->start_index, BT_HAS_PRESET_INDEX_LAST, preset_found, &preset); if (preset == NULL) { return BT_ATT_ERR_OUT_OF_RANGE; } /* Reject if already in progress */ if (read_presets_req_is_pending(client)) { return BT_HAS_ERR_OPERATION_NOT_POSSIBLE; } /* Store the request */ client->read_presets_req.start_index = req->start_index; client->read_presets_req.num_presets = req->num_presets; k_work_submit(&client->control_point_work); return 0; } static int set_preset_name(uint8_t index, const char *name, size_t len) { struct has_preset *preset = NULL; LOG_DBG("index %d name_len %zu", index, len); if (len < BT_HAS_PRESET_NAME_MIN || len > BT_HAS_PRESET_NAME_MAX) { return -EINVAL; } /* Abort if there is no preset in requested index range */ preset_foreach(index, BT_HAS_PRESET_INDEX_LAST, preset_found, &preset); if (preset == NULL) { return -ENOENT; } if (!(preset->properties & BT_HAS_PROP_WRITABLE)) { return -EPERM; } IF_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC, ( __ASSERT(len < ARRAY_SIZE(preset->name), "No space for name"); (void)memcpy(preset->name, name, len); /* NULL-terminate string */ preset->name[len] = '\0'; /* Properly truncate a NULL-terminated UTF-8 string */ utf8_trunc(preset->name); )); if (preset->ops->name_changed) { preset->ops->name_changed(index, preset->name); } return bt_has_cp_generic_update(NULL, preset, BT_HAS_IS_LAST); } static uint8_t handle_write_preset_name(struct bt_conn *conn, struct net_buf_simple *buf) { const struct bt_has_cp_write_preset_name *req; struct has_client *client; int err; if (buf->len < sizeof(*req)) { return BT_HAS_ERR_INVALID_PARAM_LEN; } /* As per HAS_v1.0 Client Characteristic Configuration Descriptor Improperly Configured * shall be returned if client writes Write Preset Name opcode but is not registered for * indications. */ if (!bt_gatt_is_subscribed(conn, PRESET_CONTROL_POINT_ATTR, BT_GATT_CCC_INDICATE)) { return BT_ATT_ERR_CCC_IMPROPER_CONF; } client = client_get(conn); if (!client) { return BT_ATT_ERR_UNLIKELY; } req = net_buf_simple_pull_mem(buf, sizeof(*req)); err = set_preset_name(req->index, req->name, buf->len); if (err == -EINVAL) { return BT_HAS_ERR_INVALID_PARAM_LEN; } else if (err == -ENOENT) { return BT_ATT_ERR_OUT_OF_RANGE; } else if (err == -EPERM) { return BT_HAS_ERR_WRITE_NAME_NOT_ALLOWED; } else if (err) { return BT_ATT_ERR_UNLIKELY; } return BT_ATT_ERR_SUCCESS; } static void active_preset_work_process(struct k_work *work) { const uint8_t active_index = bt_has_preset_active_get(); bt_gatt_notify(NULL, ACTIVE_PRESET_INDEX_ATTR, &active_index, sizeof(active_index)); } static void preset_active_set(uint8_t index) { if (index != has.active_index) { has.active_index = index; /* Emit active preset notification */ k_work_submit(&active_preset_work); } } static uint8_t preset_select(const struct has_preset *preset, bool sync) { const int err = preset->ops->select(preset->index, sync); if (err == -EINPROGRESS) { /* User has to confirm once the requested preset becomes active by * calling bt_has_preset_active_set. */ return 0; } if (err == -EBUSY) { return BT_HAS_ERR_OPERATION_NOT_POSSIBLE; } if (err) { return BT_ATT_ERR_UNLIKELY; } preset_active_set(preset->index); return 0; } static uint8_t handle_set_active_preset(struct net_buf_simple *buf, bool sync) { const struct bt_has_cp_set_active_preset *pdu; const struct has_preset *preset = NULL; if (buf->len < sizeof(*pdu)) { return BT_HAS_ERR_INVALID_PARAM_LEN; } pdu = net_buf_simple_pull_mem(buf, sizeof(*pdu)); preset_foreach(pdu->index, pdu->index, preset_found, &preset); if (preset == NULL) { return BT_ATT_ERR_OUT_OF_RANGE; } if (!(preset->properties & BT_HAS_PROP_AVAILABLE)) { return BT_HAS_ERR_OPERATION_NOT_POSSIBLE; } return preset_select(preset, sync); } static uint8_t handle_set_next_preset(bool sync) { const struct has_preset *next_avail = NULL; const struct has_preset *first_avail = NULL; for (size_t i = 0; i < has_preset_num; i++) { const struct has_preset *tmp = &has_preset_list[i]; if (tmp->index == BT_HAS_PRESET_INDEX_NONE) { break; } if (!(tmp->properties & BT_HAS_PROP_AVAILABLE)) { continue; } if (tmp->index < has.active_index && !first_avail) { first_avail = tmp; continue; } if (tmp->index > has.active_index) { next_avail = tmp; break; } } if (next_avail) { return preset_select(next_avail, sync); } if (first_avail) { return preset_select(first_avail, sync); } return BT_HAS_ERR_OPERATION_NOT_POSSIBLE; } static uint8_t handle_set_prev_preset(bool sync) { const struct has_preset *prev_available = NULL; const struct has_preset *last_available = NULL; for (size_t i = 0; i < ARRAY_SIZE(has_preset_list); i++) { const struct has_preset *tmp = &has_preset_list[i]; if (tmp->index == BT_HAS_PRESET_INDEX_NONE) { break; } if (!(tmp->properties & BT_HAS_PROP_AVAILABLE)) { continue; } if (tmp->index < has.active_index) { prev_available = tmp; continue; } if (prev_available) { break; } if (tmp->index > has.active_index) { last_available = tmp; continue; } } if (prev_available) { return preset_select(prev_available, sync); } if (last_available) { return preset_select(last_available, sync); } return BT_HAS_ERR_OPERATION_NOT_POSSIBLE; } static uint8_t handle_control_point_op(struct bt_conn *conn, struct net_buf_simple *buf) { const struct bt_has_cp_hdr *hdr; hdr = net_buf_simple_pull_mem(buf, sizeof(*hdr)); LOG_DBG("conn %p opcode %s (0x%02x)", (void *)conn, bt_has_op_str(hdr->opcode), hdr->opcode); switch (hdr->opcode) { case BT_HAS_OP_READ_PRESET_REQ: return handle_read_preset_req(conn, buf); case BT_HAS_OP_WRITE_PRESET_NAME: if (IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)) { return handle_write_preset_name(conn, buf); } break; case BT_HAS_OP_SET_ACTIVE_PRESET: return handle_set_active_preset(buf, false); case BT_HAS_OP_SET_NEXT_PRESET: return handle_set_next_preset(false); case BT_HAS_OP_SET_PREV_PRESET: return handle_set_prev_preset(false); case BT_HAS_OP_SET_ACTIVE_PRESET_SYNC: if ((has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) != 0) { return handle_set_active_preset(buf, true); } else { return BT_HAS_ERR_PRESET_SYNC_NOT_SUPP; } case BT_HAS_OP_SET_NEXT_PRESET_SYNC: if ((has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) != 0) { return handle_set_next_preset(true); } else { return BT_HAS_ERR_PRESET_SYNC_NOT_SUPP; } case BT_HAS_OP_SET_PREV_PRESET_SYNC: if ((has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) != 0) { return handle_set_prev_preset(true); } else { return BT_HAS_ERR_PRESET_SYNC_NOT_SUPP; } }; return BT_HAS_ERR_INVALID_OPCODE; } static ssize_t write_control_point(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *data, uint16_t len, uint16_t offset, uint8_t flags) { struct net_buf_simple buf; uint8_t err; LOG_DBG("conn %p attr %p data %p len %d offset %d flags 0x%02x", (void *)conn, attr, data, len, offset, flags); if (offset > 0) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } if (len == 0) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } net_buf_simple_init_with_data(&buf, (void *)data, len); err = handle_control_point_op(conn, &buf); if (err) { LOG_WRN("err 0x%02x", err); return BT_GATT_ERR(err); } return len; } int bt_has_preset_register(const struct bt_has_preset_register_param *param) { struct has_preset *preset = NULL; size_t name_len; CHECKIF(param == NULL) { LOG_ERR("param is NULL"); return -EINVAL; } CHECKIF(param->index == BT_HAS_PRESET_INDEX_NONE) { LOG_ERR("param->index is invalid"); return -EINVAL; } CHECKIF(param->name == NULL) { LOG_ERR("param->name is NULL"); return -EINVAL; } name_len = strlen(param->name); CHECKIF(name_len < BT_HAS_PRESET_NAME_MIN) { LOG_ERR("param->name is too short (%zu < %u)", name_len, BT_HAS_PRESET_NAME_MIN); return -EINVAL; } CHECKIF(name_len > BT_HAS_PRESET_NAME_MAX) { LOG_WRN("param->name is too long (%zu > %u)", name_len, BT_HAS_PRESET_NAME_MAX); } CHECKIF(param->ops == NULL) { LOG_ERR("param->ops is NULL"); return -EINVAL; } CHECKIF(param->ops->select == NULL) { LOG_ERR("param->ops->select is NULL"); return -EINVAL; } preset_foreach(param->index, param->index, preset_found, &preset); if (preset != NULL) { return -EALREADY; } preset = preset_alloc(param->index, param->properties, param->name, param->ops); if (preset == NULL) { return -ENOMEM; } return bt_has_cp_generic_update(NULL, preset, BT_HAS_IS_LAST); } int bt_has_preset_unregister(uint8_t index) { struct has_preset *preset = NULL; NET_BUF_SIMPLE_DEFINE(buf, sizeof(struct bt_has_cp_hdr) + sizeof(struct bt_has_cp_preset_changed) + sizeof(uint8_t)); CHECKIF(index == BT_HAS_PRESET_INDEX_NONE) { LOG_ERR("index is invalid"); return -EINVAL; } preset_foreach(index, index, preset_found, &preset); if (preset == NULL) { return -ENOENT; } preset_changed_prepare(&buf, BT_HAS_CHANGE_ID_PRESET_DELETED, BT_HAS_IS_LAST); net_buf_simple_add_u8(&buf, preset->index); preset_free(preset); return control_point_send_all(&buf); } int bt_has_preset_available(uint8_t index) { struct has_preset *preset = NULL; CHECKIF(index == BT_HAS_PRESET_INDEX_NONE) { LOG_ERR("index is invalid"); return -EINVAL; } preset_foreach(index, index, preset_found, &preset); if (preset == NULL) { return -ENOENT; } /* toggle property bit if needed */ if (!(preset->properties & BT_HAS_PROP_AVAILABLE)) { NET_BUF_SIMPLE_DEFINE(buf, sizeof(struct bt_has_cp_hdr) + sizeof(struct bt_has_cp_preset_changed) + sizeof(uint8_t)); preset->properties ^= BT_HAS_PROP_AVAILABLE; preset_changed_prepare(&buf, BT_HAS_CHANGE_ID_PRESET_AVAILABLE, BT_HAS_IS_LAST); net_buf_simple_add_u8(&buf, preset->index); return control_point_send_all(&buf); } return 0; } int bt_has_preset_unavailable(uint8_t index) { struct has_preset *preset = NULL; CHECKIF(index == BT_HAS_PRESET_INDEX_NONE) { LOG_ERR("index is invalid"); return -EINVAL; } preset_foreach(index, index, preset_found, &preset); if (preset == NULL) { return -ENOENT; } /* toggle property bit if needed */ if (preset->properties & BT_HAS_PROP_AVAILABLE) { NET_BUF_SIMPLE_DEFINE(buf, sizeof(struct bt_has_cp_hdr) + sizeof(struct bt_has_cp_preset_changed) + sizeof(uint8_t)); preset->properties ^= BT_HAS_PROP_AVAILABLE; preset_changed_prepare(&buf, BT_HAS_CHANGE_ID_PRESET_UNAVAILABLE, BT_HAS_IS_LAST); net_buf_simple_add_u8(&buf, preset->index); return control_point_send_all(&buf); } return 0; } struct bt_has_preset_foreach_data { bt_has_preset_func_t func; void *user_data; }; static uint8_t bt_has_preset_foreach_func(const struct has_preset *preset, void *user_data) { const struct bt_has_preset_foreach_data *data = user_data; return data->func(preset->index, preset->properties, preset->name, data->user_data); } void bt_has_preset_foreach(uint8_t index, bt_has_preset_func_t func, void *user_data) { uint8_t start_index, end_index; struct bt_has_preset_foreach_data data = { .func = func, .user_data = user_data, }; if (index == BT_HAS_PRESET_INDEX_NONE) { start_index = BT_HAS_PRESET_INDEX_FIRST; end_index = BT_HAS_PRESET_INDEX_LAST; } else { start_index = end_index = index; } preset_foreach(start_index, end_index, bt_has_preset_foreach_func, &data); } int bt_has_preset_active_set(uint8_t index) { if (index != BT_HAS_PRESET_INDEX_NONE) { struct has_preset *preset = NULL; preset_foreach(index, index, preset_found, &preset); if (preset == NULL) { return -ENOENT; } if (!(preset->properties & BT_HAS_PROP_AVAILABLE)) { return -EINVAL; } } preset_active_set(index); return 0; } uint8_t bt_has_preset_active_get(void) { return has.active_index; } int bt_has_preset_name_change(uint8_t index, const char *name) { CHECKIF(name == NULL) { return -EINVAL; } if (IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)) { return set_preset_name(index, name, strlen(name)); } else { return -EOPNOTSUPP; } } #endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ int bt_has_register(const struct bt_has_register_param *param) { int err; LOG_DBG("param %p", param); CHECKIF(!param) { LOG_DBG("NULL params pointer"); return -EINVAL; } /* Initialize the supported features characteristic value */ has.features = param->type; if (IS_ENABLED(CONFIG_BT_HAS_PRESET_SUPPORT)) { has.features |= BT_HAS_FEAT_DYNAMIC_PRESETS; if (param->preset_sync_support) { if (param->type != BT_HAS_HEARING_AID_TYPE_BINAURAL) { LOG_DBG("Preset sync support only available " "for binaural hearing aid type"); return -EINVAL; } has.features |= BT_HAS_FEAT_PRESET_SYNC_SUPP; } if (param->independent_presets) { if (param->type != BT_HAS_HEARING_AID_TYPE_BINAURAL) { LOG_DBG("Independent presets only available " "for binaural hearing aid type"); return -EINVAL; } has.features |= BT_HAS_FEAT_INDEPENDENT_PRESETS; } } if (IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)) { has.features |= BT_HAS_FEAT_WRITABLE_PRESETS_SUPP; } has_svc = (struct bt_gatt_service)BT_GATT_SERVICE(has_attrs); err = bt_gatt_service_register(&has_svc); if (err != 0) { LOG_DBG("HAS service register failed: %d", err); return err; } #if defined(CONFIG_BT_HAS_PRESET_SUPPORT) k_work_init(&active_preset_work, active_preset_work_process); #endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ return 0; }