root/net/bluetooth/aosp.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2021 Intel Corporation
 */

#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>

#include "aosp.h"

/* Command complete parameters of LE_Get_Vendor_Capabilities_Command
 * The parameters grow over time. The base version that declares the
 * version_supported field is v0.95. Refer to
 * https://cs.android.com/android/platform/superproject/+/master:system/
 *         bt/gd/hci/controller.cc;l=452?q=le_get_vendor_capabilities_handler
 */
struct aosp_rp_le_get_vendor_capa {
        /* v0.95: 15 octets */
        __u8    status;
        __u8    max_advt_instances;
        __u8    offloaded_resolution_of_private_address;
        __le16  total_scan_results_storage;
        __u8    max_irk_list_sz;
        __u8    filtering_support;
        __u8    max_filter;
        __u8    activity_energy_info_support;
        __le16  version_supported;
        __le16  total_num_of_advt_tracked;
        __u8    extended_scan_support;
        __u8    debug_logging_supported;
        /* v0.96: 16 octets */
        __u8    le_address_generation_offloading_support;
        /* v0.98: 21 octets */
        __le32  a2dp_source_offload_capability_mask;
        __u8    bluetooth_quality_report_support;
        /* v1.00: 25 octets */
        __le32  dynamic_audio_buffer_support;
} __packed;

#define VENDOR_CAPA_BASE_SIZE           15
#define VENDOR_CAPA_0_98_SIZE           21

void aosp_do_open(struct hci_dev *hdev)
{
        struct sk_buff *skb;
        struct aosp_rp_le_get_vendor_capa *rp;
        u16 version_supported;

        if (!hdev->aosp_capable)
                return;

        bt_dev_dbg(hdev, "Initialize AOSP extension");

        /* LE Get Vendor Capabilities Command */
        skb = __hci_cmd_sync(hdev, hci_opcode_pack(0x3f, 0x153), 0, NULL,
                             HCI_CMD_TIMEOUT);
        if (IS_ERR_OR_NULL(skb)) {
                if (!skb)
                        skb = ERR_PTR(-EIO);

                bt_dev_err(hdev, "AOSP get vendor capabilities (%ld)",
                           PTR_ERR(skb));
                return;
        }

        /* A basic length check */
        if (skb->len < VENDOR_CAPA_BASE_SIZE)
                goto length_error;

        rp = (struct aosp_rp_le_get_vendor_capa *)skb->data;

        version_supported = le16_to_cpu(rp->version_supported);
        /* AOSP displays the version number like v0.98, v1.00, etc. */
        bt_dev_info(hdev, "AOSP extensions version v%u.%02u",
                    version_supported >> 8, version_supported & 0xff);

        /* Do not support very old versions. */
        if (version_supported < 95) {
                bt_dev_warn(hdev, "AOSP capabilities version %u too old",
                            version_supported);
                goto done;
        }

        if (version_supported < 98) {
                bt_dev_warn(hdev, "AOSP quality report is not supported");
                goto done;
        }

        if (skb->len < VENDOR_CAPA_0_98_SIZE)
                goto length_error;

        /* The bluetooth_quality_report_support is defined at version
         * v0.98. Refer to
         * https://cs.android.com/android/platform/superproject/+/
         *         master:system/bt/gd/hci/controller.cc;l=477
         */
        if (rp->bluetooth_quality_report_support) {
                hdev->aosp_quality_report = true;
                bt_dev_info(hdev, "AOSP quality report is supported");
        }

        goto done;

length_error:
        bt_dev_err(hdev, "AOSP capabilities length %d too short", skb->len);

done:
        kfree_skb(skb);
}

void aosp_do_close(struct hci_dev *hdev)
{
        if (!hdev->aosp_capable)
                return;

        bt_dev_dbg(hdev, "Cleanup of AOSP extension");
}

/* BQR command */
#define BQR_OPCODE                      hci_opcode_pack(0x3f, 0x015e)

/* BQR report action */
#define REPORT_ACTION_ADD               0x00
#define REPORT_ACTION_DELETE            0x01
#define REPORT_ACTION_CLEAR             0x02

/* BQR event masks */
#define QUALITY_MONITORING              BIT(0)
#define APPRAOCHING_LSTO                BIT(1)
#define A2DP_AUDIO_CHOPPY               BIT(2)
#define SCO_VOICE_CHOPPY                BIT(3)

#define DEFAULT_BQR_EVENT_MASK  (QUALITY_MONITORING | APPRAOCHING_LSTO | \
                                 A2DP_AUDIO_CHOPPY | SCO_VOICE_CHOPPY)

/* Reporting at milliseconds so as not to stress the controller too much.
 * Range: 0 ~ 65535 ms
 */
#define DEFALUT_REPORT_INTERVAL_MS      5000

struct aosp_bqr_cp {
        __u8    report_action;
        __u32   event_mask;
        __u16   min_report_interval;
} __packed;

static int enable_quality_report(struct hci_dev *hdev)
{
        struct sk_buff *skb;
        struct aosp_bqr_cp cp;

        cp.report_action = REPORT_ACTION_ADD;
        cp.event_mask = DEFAULT_BQR_EVENT_MASK;
        cp.min_report_interval = DEFALUT_REPORT_INTERVAL_MS;

        skb = __hci_cmd_sync(hdev, BQR_OPCODE, sizeof(cp), &cp,
                             HCI_CMD_TIMEOUT);
        if (IS_ERR_OR_NULL(skb)) {
                if (!skb)
                        skb = ERR_PTR(-EIO);

                bt_dev_err(hdev, "Enabling Android BQR failed (%ld)",
                           PTR_ERR(skb));
                return PTR_ERR(skb);
        }

        kfree_skb(skb);
        return 0;
}

static int disable_quality_report(struct hci_dev *hdev)
{
        struct sk_buff *skb;
        struct aosp_bqr_cp cp = { 0 };

        cp.report_action = REPORT_ACTION_CLEAR;

        skb = __hci_cmd_sync(hdev, BQR_OPCODE, sizeof(cp), &cp,
                             HCI_CMD_TIMEOUT);
        if (IS_ERR_OR_NULL(skb)) {
                if (!skb)
                        skb = ERR_PTR(-EIO);

                bt_dev_err(hdev, "Disabling Android BQR failed (%ld)",
                           PTR_ERR(skb));
                return PTR_ERR(skb);
        }

        kfree_skb(skb);
        return 0;
}

bool aosp_has_quality_report(struct hci_dev *hdev)
{
        return hdev->aosp_quality_report;
}

int aosp_set_quality_report(struct hci_dev *hdev, bool enable)
{
        if (!aosp_has_quality_report(hdev))
                return -EOPNOTSUPP;

        bt_dev_dbg(hdev, "quality report enable %d", enable);

        /* Enable or disable the quality report feature. */
        if (enable)
                return enable_quality_report(hdev);
        else
                return disable_quality_report(hdev);
}