| /* |
| * Copyright (C) 2021 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. |
| */ |
| |
| #include <fcntl.h> |
| #include <sys/poll.h> |
| #include <sys/uio.h> |
| #include <termios.h> |
| #include <unistd.h> |
| #include <iomanip> |
| #include <ios> |
| #include <optional> |
| |
| #include <gflags/gflags.h> |
| |
| #include "android-base/logging.h" |
| |
| #include "model/devices/h4_packetizer.h" |
| |
| // Copied from net/bluetooth/hci.h |
| #define HCI_ACLDATA_PKT 0x02 |
| #define HCI_SCODATA_PKT 0x03 |
| #define HCI_EVENT_PKT 0x04 |
| #define HCI_ISODATA_PKT 0x05 |
| #define HCI_VENDOR_PKT 0xff |
| #define HCI_MAX_ACL_SIZE 1024 |
| #define HCI_MAX_FRAME_SIZE (HCI_MAX_ACL_SIZE + 4) |
| |
| // Include H4 header byte, and reserve more buffer size in the case of excess |
| // packet. |
| constexpr const size_t kBufferSize = (HCI_MAX_FRAME_SIZE + 1) * 2; |
| |
| constexpr const char* kVhciDev = "/dev/vhci"; |
| DEFINE_string(virtio_console_dev, "", "virtio-console device path"); |
| |
| ssize_t send(int fd_, uint8_t type, const uint8_t* data, size_t length) { |
| struct iovec iov[] = {{&type, sizeof(type)}, |
| {const_cast<uint8_t*>(data), length}}; |
| ssize_t ret = 0; |
| do { |
| ret = TEMP_FAILURE_RETRY(writev(fd_, iov, sizeof(iov) / sizeof(iov[0]))); |
| } while (-1 == ret && EAGAIN == errno); |
| if (ret == -1) { |
| PLOG(ERROR) << "virtio-console to vhci failed"; |
| } |
| return ret; |
| } |
| |
| ssize_t forward(int from, int to, std::optional<unsigned char> filter_out, |
| unsigned char* buf) { |
| ssize_t count = TEMP_FAILURE_RETRY(read(from, buf, kBufferSize)); |
| if (count < 0) { |
| PLOG(ERROR) << "read failed"; |
| return count; |
| } else if (count == 0) { |
| return count; |
| } |
| if (filter_out && buf[0] == *filter_out) { |
| LOG(INFO) << "ignore 0x" << std::hex << std::setw(2) << std::setfill('0') |
| << (unsigned)buf[0] << " packet"; |
| return 0; |
| } |
| count = TEMP_FAILURE_RETRY(write(to, buf, count)); |
| if (count < 0) { |
| PLOG(ERROR) << "write failed, type: 0x" << std::hex << std::setw(2) |
| << std::setfill('0') << (unsigned)buf[0]; |
| } |
| return count; |
| } |
| |
| ssize_t forward(int from, int to, unsigned char* buf) { |
| return forward(from, to, std::nullopt, buf); |
| } |
| |
| int setTerminalRaw(int fd_) { |
| termios terminal_settings; |
| int rval = tcgetattr(fd_, &terminal_settings); |
| if (rval < 0) { |
| return rval; |
| } |
| cfmakeraw(&terminal_settings); |
| rval = tcsetattr(fd_, TCSANOW, &terminal_settings); |
| return rval; |
| } |
| |
| int main(int argc, char** argv) { |
| gflags::ParseCommandLineFlags(&argc, &argv, true); |
| |
| int vhci_fd = open(kVhciDev, O_RDWR); |
| int virtio_fd = open(FLAGS_virtio_console_dev.c_str(), O_RDWR); |
| setTerminalRaw(virtio_fd); |
| |
| struct pollfd fds[2]; |
| |
| fds[0].fd = vhci_fd; |
| fds[0].events = POLLIN; |
| fds[1].fd = virtio_fd; |
| fds[1].events = POLLIN; |
| unsigned char buf[kBufferSize]; |
| |
| auto h4 = test_vendor_lib::H4Packetizer( |
| virtio_fd, |
| [](const std::vector<uint8_t>& /* raw_command */) { |
| LOG(ERROR) |
| << "Unexpected command: command pkt shouldn't be sent as response."; |
| }, |
| [vhci_fd](const std::vector<uint8_t>& raw_event) { |
| send(vhci_fd, HCI_EVENT_PKT, raw_event.data(), raw_event.size()); |
| }, |
| [vhci_fd](const std::vector<uint8_t>& raw_acl) { |
| send(vhci_fd, HCI_ACLDATA_PKT, raw_acl.data(), raw_acl.size()); |
| }, |
| [vhci_fd](const std::vector<uint8_t>& raw_sco) { |
| send(vhci_fd, HCI_SCODATA_PKT, raw_sco.data(), raw_sco.size()); |
| }, |
| [vhci_fd](const std::vector<uint8_t>& raw_iso) { |
| send(vhci_fd, HCI_ISODATA_PKT, raw_iso.data(), raw_iso.size()); |
| }, |
| []() { LOG(INFO) << "HCI socket device disconnected"; }); |
| while (true) { |
| int ret = TEMP_FAILURE_RETRY(poll(fds, 2, -1)); |
| if (ret < 0) { |
| PLOG(ERROR) << "poll failed"; |
| continue; |
| } |
| if (fds[0].revents & (POLLIN | POLLERR)) { |
| // TODO(b/182245475) Ignore HCI_VENDOR_PKT |
| // because root-canal cannot handle it. |
| ssize_t c = forward(vhci_fd, virtio_fd, HCI_VENDOR_PKT, buf); |
| if (c < 0) { |
| PLOG(ERROR) << "vhci to virtio-console failed"; |
| } |
| } |
| if (fds[1].revents & POLLHUP) { |
| LOG(ERROR) << "PollHUP"; |
| usleep(50 * 1000); |
| continue; |
| } |
| if (fds[1].revents & (POLLIN | POLLERR)) { |
| // 'virtio-console to vhci' depends on H4Packetizer because vhci expects |
| // full packet, but the data from virtio-console could be partial. |
| h4.OnDataReady(virtio_fd); |
| } |
| } |
| } |