Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 13ce51da authored by Joseph Hwang's avatar Joseph Hwang
Browse files

gd/hal: use the MGMT socket to interface with kernel

This patch uses the MGMT socket to interface with the kernel.
The purpose is to query the Bluetooth controller capabilities,
e.g., whether the Microsoft Extension is supported, etc. This
MGMT interface will be replaced by the sockopt/ioctl which will
be upstreamed to the kernel.

Bug: 246398494
Tag: #floss
Test: ./build.py --target test
Test: Manual test with next patch.

Change-Id: Ib2bd9d0ee4e8572a70935d68e68c52b260da2949
parent 78a06c04
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -22,7 +22,10 @@ source_set("BluetoothHalSources") {
}

source_set("BluetoothHalSources_hci_host") {
  sources = [ "hci_hal_host.cc" ]
  sources = [
    "hci_hal_host.cc",
    "mgmt.cc",
  ]

  configs += [ "//bt/system/gd:gd_defaults" ]
  deps = [ "//bt/system/gd:gd_default_deps" ]

system/gd/hal/mgmt.cc

0 → 100644
+169 −0
Original line number Diff line number Diff line
/******************************************************************************
 *
 *  Copyright 2022 The Android Open Source Project
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

/*
 * TODO(b/249193511): Replace this MGMT interface with sockopt/ioctl.
 * This file will be replaced such that it is not optimized for now.
 */

#include "hal/mgmt.h"

#include <errno.h>
#include <poll.h>
#include <sys/socket.h>
#include <unistd.h>

#include "common/init_flags.h"
#include "os/log.h"

namespace bluetooth {
namespace hal {

#define RETRY_ON_INTR(fn) \
  do {                    \
  } while ((fn) == -1 && errno == EINTR)

struct sockaddr_hci {
  sa_family_t hci_family;
  unsigned short hci_dev;
  unsigned short hci_channel;
};

constexpr static uint8_t BTPROTO_HCI = 1;
constexpr static uint16_t HCI_CHANNEL_CONTROL = 3;
constexpr static uint16_t HCI_DEV_NONE = 0xffff;

static int btsocket_open_mgmt(uint16_t hci) {
  int fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_NONBLOCK, BTPROTO_HCI);
  if (fd < 0) {
    LOG_ERROR("Failed to open BT socket.");
    return -errno;
  }

  struct sockaddr_hci addr = {
      .hci_family = AF_BLUETOOTH,
      .hci_dev = HCI_DEV_NONE,
      .hci_channel = HCI_CHANNEL_CONTROL,
  };

  int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
  if (ret < 0) {
    LOG_ERROR("Failed to bind BT socket.");
    close(fd);
    return -errno;
  }

  return fd;
}

/*
 * Given a vendor specification, e.g., MSFT extension, this function returns
 * the vendor specific opcode.
 *
 * If the controller does not support MSFT extension or there are errors
 * or failures in writing/reading the MGMT socket, the return opcode would
 * be HCI_OP_NOP (0x0000).
 */
uint16_t Mgmt::get_vs_opcode(uint16_t vendor_specification) {
  int hci = bluetooth::common::InitFlags::GetAdapterIndex();
  int fd = btsocket_open_mgmt(hci);
  uint16_t ret_opcode = HCI_OP_NOP;

  if (fd < 0) {
    LOG_ERROR("Failed to open mgmt channel for hci %d, error= %d.", hci, fd);
    return ret_opcode;
  }

  struct mgmt_pkt ev;
  ev.opcode = MGMT_OP_GET_VS_OPCODE;
  ev.index = HCI_DEV_NONE;
  ev.len = sizeof(struct mgmt_cp_get_vs_opcode);

  struct mgmt_cp_get_vs_opcode* cp = reinterpret_cast<struct mgmt_cp_get_vs_opcode*>(ev.data);
  cp->hci_id = hci;
  cp->vendor_specification = MGMT_VS_OPCODE_MSFT;

  int ret;
  struct pollfd writable[1];
  writable[0].fd = fd;
  writable[0].events = POLLOUT;

  do {
    ret = poll(writable, 1, MGMT_POLL_TIMEOUT_MS);
    if (ret > 0) {
      RETRY_ON_INTR(ret = write(fd, &ev, MGMT_PKT_HDR_SIZE + ev.len));
      if (ret < 0) {
        LOG_ERROR("Failed to call MGMT opcode 0x%4.4x, errno %d", ev.opcode, -errno);
        close(fd);
        return ret_opcode;
      };
      break;
    } else if (ret < 0) {
      LOG_ERROR("msft poll ret %d errno %d", ret, -errno);
    }
  } while (ret > 0);

  if (ret <= 0) {
    LOG_INFO("Skip because mgmt socket is not writable: ev.opcode 0x%4.4x ret %d", ev.opcode, ret);
    close(fd);
    return ret_opcode;
  }

  struct pollfd fds[1];
  struct mgmt_pkt cc_ev;
  fds[0].fd = fd;
  fds[0].events = POLLIN;

  do {
    ret = poll(fds, 1, MGMT_POLL_TIMEOUT_MS);
    if (ret > 0) {
      if (fds[0].revents & POLLIN) {
        RETRY_ON_INTR(ret = read(fd, &cc_ev, sizeof(cc_ev)));
        if (ret < 0) {
          LOG_ERROR("Failed to read mgmt socket: %d", -errno);
          close(fd);
          return ret_opcode;
        }

        if (cc_ev.opcode == MGMT_EV_COMMAND_COMPLETE) {
          struct mgmt_ev_cmd_complete* cc = reinterpret_cast<struct mgmt_ev_cmd_complete*>(cc_ev.data);
          if (cc->opcode == ev.opcode && cc->status == 0) {
            struct mgmt_rp_get_vs_opcode* rp = reinterpret_cast<struct mgmt_rp_get_vs_opcode*>(cc->data);
            if (rp->hci_id == hci) {
              // If the controller supports the MSFT extension, the returned opcode
              // will not be HCI_OP_NOP.
              if (rp->opcode != HCI_OP_NOP) {
                ret_opcode = rp->opcode;
              }
              close(fd);
              return ret_opcode;
            }
          }
        }
      }
    } else if (ret == 0) {
      LOG_ERROR("Timeout while waiting for response of calling MGMT opcode: 0x%4.4x", ev.opcode);
      ret = -1;
    }
  } while (ret > 0);
  close(fd);
  return ret_opcode;
}

}  // namespace hal
}  // namespace bluetooth

system/gd/hal/mgmt.h

0 → 100644
+65 −0
Original line number Diff line number Diff line
/******************************************************************************
 *
 *  Copyright (C) 2022 The Android Open Source Project
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

#pragma once

#include <inttypes.h>

namespace bluetooth {
namespace hal {

#define HCI_OP_NOP 0x0000

#define MGMT_EV_SIZE_MAX 1024
#define MGMT_PKT_HDR_SIZE 6
struct mgmt_pkt {
  uint16_t opcode;
  uint16_t index;
  uint16_t len;
  uint8_t data[MGMT_EV_SIZE_MAX];
} __attribute__((packed));

#define MGMT_EV_COMMAND_COMPLETE 0x1
struct mgmt_ev_cmd_complete {
  uint16_t opcode;
  uint8_t status;
  uint8_t data[];
} __attribute__((packed));

#define MGMT_OP_GET_VS_OPCODE 0x0059
#define MGMT_VS_OPCODE_MSFT 0x0001
struct mgmt_cp_get_vs_opcode {
  uint16_t hci_id;
  uint16_t vendor_specification;
} __attribute__((packed));

struct mgmt_rp_get_vs_opcode {
  uint16_t hci_id;
  uint16_t opcode;
} __attribute__((packed));

#define MGMT_POLL_TIMEOUT_MS 2000

// This class provides an interface to interact with the kernel.
class Mgmt {
 public:
  uint16_t get_vs_opcode(uint16_t vendor_specification);
};

}  // namespace hal
}  // namespace bluetooth