| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fcntl.h> |
| #include <fuchsia/hardware/telephony/transport/llcpp/fidl.h> |
| #include <fuchsia/hardware/usb/peripheral/llcpp/fidl.h> |
| #include <fuchsia/hardware/usb/virtual/bus/llcpp/fidl.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fdio/watcher.h> |
| #include <lib/gtest/real_loop_fixture.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/port.h> |
| #include <unistd.h> |
| #include <zircon/hw/usb.h> |
| |
| #include <fbl/string.h> |
| #include <gtest/gtest.h> |
| #include <src/lib/isolated_devmgr/usb-virtual-bus.h> |
| |
| constexpr uint32_t kQmiBufSize = 2048LU; |
| |
| namespace { |
| |
| /// hardcoded QMI request/response |
| constexpr uint8_t kQmiImeiReq[]{1, 12, 0, 0, 2, 1, 0, 1, 0, 37, 0, 0, 0}; |
| constexpr uint8_t kQmiImeiResp[]{1, 41, 0, 128, 2, 1, 2, 1, 0, 37, 0, 29, 0, 2, |
| 4, 0, 0, 0, 0, 0, 16, 1, 0, 48, 17, 15, 0, 51, |
| 53, 57, 50, 54, 48, 48, 56, 48, 49, 54, 56, 51, 53, 49}; |
| |
| const std::string isolated_devmgr_pkg_url = |
| "fuchsia-pkg://fuchsia.com/telephony-tests#meta/tel-devmgr.cmx"; |
| const std::string isolated_devmgr_svc_name = "fuchsia.tel.devmgr.IsolatedDevmgr"; |
| |
| class USBVirtualBusQmi : public usb_virtual_bus::USBVirtualBusBase { |
| public: |
| USBVirtualBusQmi() : USBVirtualBusBase(isolated_devmgr_pkg_url, isolated_devmgr_svc_name) {} |
| |
| // Initialize QMI. Asserts on failure. |
| void InitUsbQmi(fbl::String* devpath); |
| }; |
| |
| void USBVirtualBusQmi::InitUsbQmi(fbl::String* devpath) { |
| namespace usb_peripheral = ::llcpp::fuchsia::hardware::usb::peripheral; |
| usb_peripheral::DeviceDescriptor device_desc = {}; |
| device_desc.bcd_usb = htole16(0x0200); |
| device_desc.b_device_class = 0; |
| device_desc.b_device_sub_class = 0; |
| device_desc.b_device_protocol = 0; |
| device_desc.b_max_packet_size0 = 64; |
| device_desc.bcd_device = htole16(0x0100); |
| device_desc.b_num_configurations = 1; |
| device_desc.id_vendor = htole16(0x1199); |
| device_desc.id_product = htole16(0x9091); |
| usb_peripheral::FunctionDescriptor qmi_function_desc = { |
| .interface_class = USB_CLASS_VENDOR, |
| .interface_subclass = USB_SUBCLASS_VENDOR, |
| .interface_protocol = 0xff, |
| }; |
| std::vector<usb_peripheral::FunctionDescriptor> function_descs_vec; |
| function_descs_vec.push_back(qmi_function_desc); |
| SetupPeripheralDevice(std::move(device_desc), std::move(function_descs_vec)); |
| fbl::unique_fd fd(openat(devfs_root().get(), "class/qmi-transport", O_RDONLY)); |
| while (fdio_watch_directory(fd.get(), usb_virtual_bus_helper::WaitForAnyFile, ZX_TIME_INFINITE, |
| devpath) != ZX_ERR_STOP) |
| ; |
| *devpath = fbl::String::Concat({fbl::String("class/qmi-transport/"), *devpath}); |
| } |
| |
| class UsbQmiTest : public ::gtest::RealLoopFixture { |
| public: |
| void SetUp() override { |
| bus_.InitPeripheral(); |
| bus_.InitUsbQmi(&devpath_); |
| } |
| |
| void TearDown() override { |
| ASSERT_NO_FATAL_FAILURE(bus_.ClearPeripheralDeviceFunctions()); |
| |
| auto result2 = bus_.virtual_bus().Disable(); |
| ASSERT_EQ(result2.status(), ZX_OK); |
| ASSERT_EQ(result2.value().status, ZX_OK); |
| } |
| |
| USBVirtualBusQmi& GetVirtBus() { return bus_; }; |
| |
| fbl::String& GetDevPath() { return devpath_; }; |
| |
| private: |
| USBVirtualBusQmi bus_; |
| fbl::String devpath_; |
| }; |
| |
| TEST_F(UsbQmiTest, RequestImei) { |
| fbl::unique_fd fd_qmi(openat(GetVirtBus().GetRootFd(), GetDevPath().c_str(), O_RDWR)); |
| ASSERT_GT(fd_qmi.get(), 0); |
| |
| fdio_cpp::FdioCaller qmi_fdio_caller_; |
| qmi_fdio_caller_.reset(std::move(fd_qmi)); |
| zx::port channel_port; |
| // set QMI channel to driver |
| zx::channel channel_local, channel_remote; |
| ASSERT_EQ(zx::channel::create(0, &channel_local, &channel_remote), ZX_OK); |
| auto result = llcpp::fuchsia::hardware::telephony::transport::Qmi::Call::SetChannel( |
| qmi_fdio_caller_.channel(), std::move(channel_remote)); |
| ASSERT_EQ(result.status(), ZX_OK); |
| ASSERT_EQ(result->result.is_err(), false); |
| ASSERT_EQ(zx::port::create(0, &channel_port), ZX_OK); |
| channel_local.wait_async(channel_port, 0, ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, 0); |
| |
| // send QMI message requesting IMEI |
| ASSERT_EQ(channel_local.write(0, kQmiImeiReq, sizeof(kQmiImeiReq), nullptr, 0), ZX_OK); |
| |
| // validate QMI message for IMEI reply |
| zx_port_packet_t packet; |
| ASSERT_EQ(channel_port.wait(zx::time(ZX_TIME_INFINITE), &packet), ZX_OK); |
| ASSERT_EQ(packet.key, 0LU); |
| ASSERT_EQ(packet.signal.observed & ZX_CHANNEL_PEER_CLOSED, 0L); |
| std::array<uint8_t, kQmiBufSize> buffer; |
| uint32_t read_bytes; |
| uint32_t handle_num; |
| channel_local.read(0, buffer.data(), nullptr, kQmiBufSize, 0, &read_bytes, &handle_num); |
| ASSERT_EQ(handle_num, 0LU); |
| ASSERT_EQ(memcmp(buffer.data(), kQmiImeiResp, sizeof(kQmiImeiResp)), 0L); |
| } |
| |
| } // namespace |