/* Bluetooth VCP */ /* * Copyright (c) 2018 Intel Corporation * Copyright (c) 2019-2020 Bose Corporation * Copyright (c) 2020-2022 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include "audio_internal.h" #include "vcp_internal.h" #define LOG_LEVEL CONFIG_BT_VCP_VOL_REND_LOG_LEVEL #include LOG_MODULE_REGISTER(bt_vcp_vol_rend); #define VOLUME_DOWN(current_vol) \ ((uint8_t)MAX(0, (int)current_vol - vol_rend.volume_step)) #define VOLUME_UP(current_vol) \ ((uint8_t)MIN(UINT8_MAX, (int)current_vol + vol_rend.volume_step)) #define VALID_VCP_OPCODE(opcode) ((opcode) <= BT_VCP_OPCODE_MUTE) enum vol_rend_notify { NOTIFY_STATE, NOTIFY_FLAGS, NOTIFY_NUM, }; struct bt_vcp_vol_rend { struct vcs_state state; uint8_t flags; struct bt_vcp_vol_rend_cb *cb; uint8_t volume_step; struct bt_gatt_service *service_p; struct bt_vocs *vocs_insts[CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT]; struct bt_aics *aics_insts[CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT]; ATOMIC_DEFINE(notify, NOTIFY_NUM); struct k_work_delayable notify_work; }; static struct bt_vcp_vol_rend vol_rend; static void volume_state_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } static ssize_t read_vol_state(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { LOG_DBG("Volume %u, mute %u, counter %u", vol_rend.state.volume, vol_rend.state.mute, vol_rend.state.change_counter); return bt_gatt_attr_read(conn, attr, buf, len, offset, &vol_rend.state, sizeof(vol_rend.state)); } static const char *vol_rend_notify_str(enum vol_rend_notify notify) { switch (notify) { case NOTIFY_STATE: return "state"; case NOTIFY_FLAGS: return "flags"; default: return "unknown"; } } static void notify_work_reschedule(struct bt_vcp_vol_rend *inst, enum vol_rend_notify notify, k_timeout_t delay) { int err; atomic_set_bit(inst->notify, notify); err = k_work_reschedule(&inst->notify_work, delay); if (err < 0) { LOG_ERR("Failed to reschedule %s notification err %d", vol_rend_notify_str(notify), err); } else if (!K_TIMEOUT_EQ(delay, K_NO_WAIT)) { LOG_DBG("%s notification scheduled in %dms", vol_rend_notify_str(notify), k_ticks_to_ms_floor32(k_work_delayable_remaining_get(&inst->notify_work))); } } static void notify(struct bt_vcp_vol_rend *inst, enum vol_rend_notify notify, const struct bt_uuid *uuid, const void *data, uint16_t len) { int err; err = bt_gatt_notify_uuid(NULL, uuid, inst->service_p->attrs, data, len); if (err == -ENOMEM) { notify_work_reschedule(inst, notify, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); } else if (err < 0 && err != -ENOTCONN) { LOG_ERR("Notify %s err %d", vol_rend_notify_str(notify), err); } } static void notify_work_handler(struct k_work *work) { struct k_work_delayable *d_work = k_work_delayable_from_work(work); struct bt_vcp_vol_rend *inst = CONTAINER_OF(d_work, struct bt_vcp_vol_rend, notify_work); if (atomic_test_and_clear_bit(inst->notify, NOTIFY_STATE)) { notify(inst, NOTIFY_STATE, BT_UUID_VCS_STATE, &inst->state, sizeof(inst->state)); } if (IS_ENABLED(CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE) && atomic_test_and_clear_bit(inst->notify, NOTIFY_FLAGS)) { notify(inst, NOTIFY_FLAGS, BT_UUID_VCS_FLAGS, &inst->flags, sizeof(inst->flags)); } } static void value_changed(struct bt_vcp_vol_rend *inst, enum vol_rend_notify notify) { notify_work_reschedule(inst, notify, K_NO_WAIT); } static ssize_t write_vcs_control(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { const struct vcs_control_vol *cp_val = buf; bool notify = false; bool volume_change = false; uint8_t opcode; if (offset > 0) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } if (len == 0 || buf == NULL) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } /* Check opcode before length */ if (!VALID_VCP_OPCODE(cp_val->cp.opcode)) { LOG_DBG("Invalid opcode %u", cp_val->cp.opcode); return BT_GATT_ERR(BT_VCP_ERR_OP_NOT_SUPPORTED); } if ((len < sizeof(struct vcs_control)) || (len == sizeof(struct vcs_control_vol) && cp_val->cp.opcode != BT_VCP_OPCODE_SET_ABS_VOL) || (len > sizeof(struct vcs_control_vol))) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } opcode = cp_val->cp.opcode; LOG_DBG("Opcode %u, counter %u", opcode, cp_val->cp.counter); if (cp_val->cp.counter != vol_rend.state.change_counter) { return BT_GATT_ERR(BT_VCP_ERR_INVALID_COUNTER); } switch (opcode) { case BT_VCP_OPCODE_REL_VOL_DOWN: LOG_DBG("Relative Volume Down (0x%x)", opcode); if (vol_rend.state.volume > 0) { vol_rend.state.volume = VOLUME_DOWN(vol_rend.state.volume); notify = true; } volume_change = true; break; case BT_VCP_OPCODE_REL_VOL_UP: LOG_DBG("Relative Volume Up (0x%x)", opcode); if (vol_rend.state.volume != UINT8_MAX) { vol_rend.state.volume = VOLUME_UP(vol_rend.state.volume); notify = true; } volume_change = true; break; case BT_VCP_OPCODE_UNMUTE_REL_VOL_DOWN: LOG_DBG("(Unmute) relative Volume Down (0x%x)", opcode); if (vol_rend.state.volume > 0) { vol_rend.state.volume = VOLUME_DOWN(vol_rend.state.volume); notify = true; } if (vol_rend.state.mute) { vol_rend.state.mute = BT_VCP_STATE_UNMUTED; notify = true; } volume_change = true; break; case BT_VCP_OPCODE_UNMUTE_REL_VOL_UP: LOG_DBG("(Unmute) relative Volume Up (0x%x)", opcode); if (vol_rend.state.volume != UINT8_MAX) { vol_rend.state.volume = VOLUME_UP(vol_rend.state.volume); notify = true; } if (vol_rend.state.mute) { vol_rend.state.mute = BT_VCP_STATE_UNMUTED; notify = true; } volume_change = true; break; case BT_VCP_OPCODE_SET_ABS_VOL: LOG_DBG("Set Absolute Volume (0x%x): Current volume %u", opcode, vol_rend.state.volume); if (vol_rend.state.volume != cp_val->volume) { vol_rend.state.volume = cp_val->volume; notify = true; } volume_change = true; break; case BT_VCP_OPCODE_UNMUTE: LOG_DBG("Unmute (0x%x)", opcode); if (vol_rend.state.mute) { vol_rend.state.mute = BT_VCP_STATE_UNMUTED; notify = true; } break; case BT_VCP_OPCODE_MUTE: LOG_DBG("Mute (0x%x)", opcode); if (vol_rend.state.mute == BT_VCP_STATE_UNMUTED) { vol_rend.state.mute = BT_VCP_STATE_MUTED; notify = true; } break; default: LOG_DBG("Unknown opcode (0x%x)", opcode); return BT_GATT_ERR(BT_VCP_ERR_OP_NOT_SUPPORTED); } if (notify) { vol_rend.state.change_counter++; LOG_DBG("New state: volume %u, mute %u, counter %u", vol_rend.state.volume, vol_rend.state.mute, vol_rend.state.change_counter); value_changed(&vol_rend, NOTIFY_STATE); if (vol_rend.cb && vol_rend.cb->state) { vol_rend.cb->state(0, vol_rend.state.volume, vol_rend.state.mute); } } if (volume_change && !vol_rend.flags) { vol_rend.flags = 1; if (IS_ENABLED(CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE)) { value_changed(&vol_rend, NOTIFY_FLAGS); } if (vol_rend.cb && vol_rend.cb->flags) { vol_rend.cb->flags(0, vol_rend.flags); } } return len; } #if defined(CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE) static void flags_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } #endif /* CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE */ static ssize_t read_flags(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { LOG_DBG("0x%02x", vol_rend.flags); return bt_gatt_attr_read(conn, attr, buf, len, offset, &vol_rend.flags, sizeof(vol_rend.flags)); } #define DUMMY_INCLUDE(i, _) BT_GATT_INCLUDE_SERVICE(NULL), #define VOCS_INCLUDES(cnt) LISTIFY(cnt, DUMMY_INCLUDE, ()) #define AICS_INCLUDES(cnt) LISTIFY(cnt, DUMMY_INCLUDE, ()) /* Volume Control Service GATT Attributes */ static struct bt_gatt_attr vcs_attrs[] = { BT_GATT_PRIMARY_SERVICE(BT_UUID_VCS), VOCS_INCLUDES(CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT) AICS_INCLUDES(CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT) BT_AUDIO_CHRC(BT_UUID_VCS_STATE, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, read_vol_state, NULL, NULL), BT_AUDIO_CCC(volume_state_cfg_changed), BT_AUDIO_CHRC(BT_UUID_VCS_CONTROL, BT_GATT_CHRC_WRITE, BT_GATT_PERM_WRITE_ENCRYPT, NULL, write_vcs_control, NULL), #if defined(CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE) BT_AUDIO_CHRC(BT_UUID_VCS_FLAGS, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, read_flags, NULL, NULL), BT_AUDIO_CCC(flags_cfg_changed) #else BT_AUDIO_CHRC(BT_UUID_VCS_FLAGS, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, read_flags, NULL, NULL) #endif /* CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE */ }; static struct bt_gatt_service vcs_svc; static int prepare_vocs_inst(struct bt_vcp_vol_rend_register_param *param) { int err; int j; int i; if (CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT == 0) { return 0; } __ASSERT(param, "NULL param"); for (j = 0, i = 0; i < ARRAY_SIZE(vcs_attrs); i++) { if (bt_uuid_cmp(vcs_attrs[i].uuid, BT_UUID_GATT_INCLUDE) == 0 && !vcs_attrs[i].user_data) { vol_rend.vocs_insts[j] = bt_vocs_free_instance_get(); if (vol_rend.vocs_insts[j] == NULL) { LOG_ERR("Could not get free VOCS instances[%d]", j); return -ENOMEM; } err = bt_vocs_register(vol_rend.vocs_insts[j], ¶m->vocs_param[j]); if (err != 0) { LOG_DBG("Could not register VOCS instance[%d]: %d", j, err); return err; } vcs_attrs[i].user_data = bt_vocs_svc_decl_get(vol_rend.vocs_insts[j]); j++; if (j == CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT) { break; } } } __ASSERT(j == CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT, "Invalid VOCS instance count"); return 0; } static int prepare_aics_inst(struct bt_vcp_vol_rend_register_param *param) { int err; int j; int i; if (CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT == 0) { return 0; } __ASSERT(param, "NULL param"); for (j = 0, i = 0; i < ARRAY_SIZE(vcs_attrs); i++) { if (bt_uuid_cmp(vcs_attrs[i].uuid, BT_UUID_GATT_INCLUDE) == 0 && !vcs_attrs[i].user_data) { vol_rend.aics_insts[j] = bt_aics_free_instance_get(); if (vol_rend.aics_insts[j] == NULL) { LOG_ERR("Could not get free AICS instances[%d]", j); return -ENOMEM; } err = bt_aics_register(vol_rend.aics_insts[j], ¶m->aics_param[j]); if (err != 0) { LOG_DBG("Could not register AICS instance[%d]: %d", j, err); return err; } vcs_attrs[i].user_data = bt_aics_svc_decl_get(vol_rend.aics_insts[j]); j++; LOG_DBG("AICS P %p", vcs_attrs[i].user_data); if (j == CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT) { break; } } } __ASSERT(j == CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT, "Invalid AICS instance count"); return 0; } /****************************** PUBLIC API ******************************/ int bt_vcp_vol_rend_register(struct bt_vcp_vol_rend_register_param *param) { static bool registered; int err; CHECKIF(param == NULL) { LOG_DBG("param is NULL"); return -EINVAL; } CHECKIF(param->mute > BT_VCP_STATE_MUTED) { LOG_DBG("Invalid mute value: %u", param->mute); return -EINVAL; } CHECKIF(param->step == 0) { LOG_DBG("Invalid step value: %u", param->step); return -EINVAL; } if (registered) { return -EALREADY; } vcs_svc = (struct bt_gatt_service)BT_GATT_SERVICE(vcs_attrs); if (CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT > 0) { err = prepare_vocs_inst(param); if (err != 0) { return err; } } if (CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT > 0) { err = prepare_aics_inst(param); if (err != 0) { return err; } } vol_rend.state.volume = param->volume; vol_rend.state.mute = param->mute; vol_rend.volume_step = param->step; vol_rend.service_p = &vcs_svc; err = bt_gatt_service_register(&vcs_svc); if (err != 0) { LOG_DBG("VCP service register failed: %d", err); } vol_rend.cb = param->cb; atomic_clear(vol_rend.notify); k_work_init_delayable(&vol_rend.notify_work, notify_work_handler); registered = true; return err; } int bt_vcp_vol_rend_included_get(struct bt_vcp_included *included) { if (included == NULL) { return -EINVAL; } included->vocs_cnt = ARRAY_SIZE(vol_rend.vocs_insts); included->vocs = vol_rend.vocs_insts; included->aics_cnt = ARRAY_SIZE(vol_rend.aics_insts); included->aics = vol_rend.aics_insts; return 0; } int bt_vcp_vol_rend_set_step(uint8_t volume_step) { if (volume_step > 0) { vol_rend.volume_step = volume_step; return 0; } else { return -EINVAL; } } int bt_vcp_vol_rend_get_state(void) { if (vol_rend.cb && vol_rend.cb->state) { vol_rend.cb->state(0, vol_rend.state.volume, vol_rend.state.mute); } return 0; } int bt_vcp_vol_rend_get_flags(void) { if (vol_rend.cb && vol_rend.cb->flags) { vol_rend.cb->flags(0, vol_rend.flags); } return 0; } int bt_vcp_vol_rend_vol_down(void) { const struct vcs_control cp = { .opcode = BT_VCP_OPCODE_REL_VOL_DOWN, .counter = vol_rend.state.change_counter, }; int err; err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0); return err > 0 ? 0 : err; } int bt_vcp_vol_rend_vol_up(void) { const struct vcs_control cp = { .opcode = BT_VCP_OPCODE_REL_VOL_UP, .counter = vol_rend.state.change_counter, }; int err; err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0); return err > 0 ? 0 : err; } int bt_vcp_vol_rend_unmute_vol_down(void) { const struct vcs_control cp = { .opcode = BT_VCP_OPCODE_UNMUTE_REL_VOL_DOWN, .counter = vol_rend.state.change_counter, }; int err; err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0); return err > 0 ? 0 : err; } int bt_vcp_vol_rend_unmute_vol_up(void) { const struct vcs_control cp = { .opcode = BT_VCP_OPCODE_UNMUTE_REL_VOL_UP, .counter = vol_rend.state.change_counter, }; int err; err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0); return err > 0 ? 0 : err; } int bt_vcp_vol_rend_set_vol(uint8_t volume) { const struct vcs_control_vol cp = { .cp = { .opcode = BT_VCP_OPCODE_SET_ABS_VOL, .counter = vol_rend.state.change_counter }, .volume = volume }; int err; err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0); return err > 0 ? 0 : err; } int bt_vcp_vol_rend_unmute(void) { const struct vcs_control cp = { .opcode = BT_VCP_OPCODE_UNMUTE, .counter = vol_rend.state.change_counter, }; int err; err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0); return err > 0 ? 0 : err; } int bt_vcp_vol_rend_mute(void) { const struct vcs_control cp = { .opcode = BT_VCP_OPCODE_MUTE, .counter = vol_rend.state.change_counter, }; int err; err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0); return err > 0 ? 0 : err; }