[mt8167][usb] Dynamic endpoint number support.

Currently, the number of implemented endpoint controllers is hard-coded
into the host driver.  This works for the mt8167 chip, but might be
incorrect in a different chip that uses the musbmhdrc IP.  In an effort
to genericize the driver to any chip, we'll read the count of
implemented endpoint controllers from the EPINFO register.

Test: UMS iochk -c 200 /dev/class/block/xxx
Test: runtests -t mt-hci-test-test
Change-Id: I11ddab7da699ce408b840774e209fb2626942120
diff --git a/zircon/system/dev/usb/mt-musb-host/BUILD.gn b/zircon/system/dev/usb/mt-musb-host/BUILD.gn
index a99650f..e2c23bce 100644
--- a/zircon/system/dev/usb/mt-musb-host/BUILD.gn
+++ b/zircon/system/dev/usb/mt-musb-host/BUILD.gn
@@ -3,6 +3,13 @@
 # found in the LICENSE file.
 
 driver("mt-musb-host") {
+  deps = [
+    ":common",
+  ]
+}
+
+source_set("common") {
+  visibility = [ ":*" ]
   sources = [
     "usb-device.cpp",
     "usb-hci.cpp",
@@ -11,7 +18,7 @@
     "usb-spew.cpp",
     "usb-transaction.cpp",
   ]
-  deps = [
+  public_deps = [
     "$zx/system/banjo/ddk.protocol.usb.bus",
     "$zx/system/banjo/ddk.protocol.usb.hci",
     "$zx/system/dev/lib/device-protocol-pdev",
@@ -28,6 +35,18 @@
   ]
 }
 
+test("mt-hci-test") {
+  output_name = "mt-hci-test"
+  sources = [
+    "usb-hci-test.cpp",
+  ]
+  deps = [
+    ":common",
+    "$zx/system/dev/lib/fake_ddk",
+    "$zx/system/ulib/zxtest",
+  ]
+}
+
 test("mt-hci-transaction-test") {
   sources = [
     "usb-transaction-test.cpp",
diff --git a/zircon/system/dev/usb/mt-musb-host/usb-device.cpp b/zircon/system/dev/usb/mt-musb-host/usb-device.cpp
index 60248b8..04d7021 100644
--- a/zircon/system/dev/usb/mt-musb-host/usb-device.cpp
+++ b/zircon/system/dev/usb/mt-musb-host/usb-device.cpp
@@ -62,8 +62,8 @@
 }
 
 void HardwareDevice::Disconnect() {
-    for (uint8_t i=0; i<=kMaxEpNum; i++) {
-        if (ep_q_[i] != nullptr) {
+    for (uint8_t i = 0; i < kMaxEndpointCount; i++) {
+        if (ep_q_[i]) {
             ep_q_[i]->Halt();
         }
     }
@@ -170,8 +170,8 @@
 }
 
 size_t HardwareDevice::GetMaxTransferSize(uint8_t ep) {
-    if (ep > kMaxEpNum || !ep_q_[ep]) {
-        zxlogf(ERROR, "%s: unconfigured endpoint\n", __func__);
+    if (ep >= kMaxEndpointCount || !ep_q_[ep]) {
+        zxlogf(ERROR, "%s: unconfigured endpoint: %d\n", __func__, ep);
         return 0;
     }
     return ep_q_[ep]->GetMaxTransferSize();
diff --git a/zircon/system/dev/usb/mt-musb-host/usb-device.h b/zircon/system/dev/usb/mt-musb-host/usb-device.h
index 46c8c94..cfd5156 100644
--- a/zircon/system/dev/usb/mt-musb-host/usb-device.h
+++ b/zircon/system/dev/usb/mt-musb-host/usb-device.h
@@ -16,8 +16,9 @@
 
 namespace mt_usb_hci {
 
-// The (inclusive) maximum endpont number.
-constexpr int kMaxEpNum = 8;
+// The maximum number of endpoints any USB device could theoretically support.  Endpoint addresses
+// are 4-bit values.
+constexpr int kMaxEndpointCount = 16;
 
 // UsbDevice is a usb spec-compliant device.
 class UsbDevice {
@@ -103,7 +104,7 @@
     const usb_speed_t speed_;
 
     // Array of RequestQueue unique_ptrs indexed by endpoint-number.
-    std::array<std::unique_ptr<RequestQueue>, kMaxEpNum+1> ep_q_;
+    std::array<std::unique_ptr<RequestQueue>, kMaxEndpointCount> ep_q_;
 };
 
 } // namespace mt_usb_hci
diff --git a/zircon/system/dev/usb/mt-musb-host/usb-hci-test.cpp b/zircon/system/dev/usb/mt-musb-host/usb-hci-test.cpp
new file mode 100644
index 0000000..53cf56b
--- /dev/null
+++ b/zircon/system/dev/usb/mt-musb-host/usb-hci-test.cpp
@@ -0,0 +1,68 @@
+// 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 "usb-hci.h"
+
+#include <lib/fake_ddk/fake_ddk.h>
+#include <lib/mmio/mmio.h>
+#include <lib/zx/vmo.h>
+#include <soc/mt8167/mt8167-hw.h>
+#include <soc/mt8167/mt8167-usb.h>
+#include <zircon/types.h>
+#include <zxtest/zxtest.h>
+
+namespace mt_usb_hci {
+namespace regs = board_mt8167;
+
+// A testing instance which exposes the Init() method.
+class TUsbHci : public UsbHci {
+public:
+    using UsbHci::UsbHci;
+    using UsbHci::Init;
+};
+
+class HciTest: public zxtest::Test {
+protected:
+    void SetUp() {
+        size_t sz;
+
+        zx::vmo usb;
+        ASSERT_OK(zx::vmo::create(MT8167_USB1_LENGTH, 0, &usb));
+        ASSERT_OK(usb.get_size(&sz));
+        ASSERT_OK(ddk::MmioBuffer::Create(0, sz, std::move(usb), ZX_CACHE_POLICY_UNCACHED,
+                                          &usb_mmio_));
+
+        zx::vmo phy;
+        ASSERT_OK(zx::vmo::create(MT8167_USBPHY_LENGTH, 0, &phy));
+        ASSERT_OK(phy.get_size(&sz));
+        ASSERT_OK(ddk::MmioBuffer::Create(0, sz, std::move(phy), ZX_CACHE_POLICY_UNCACHED,
+                                          &phy_mmio_));
+
+        ASSERT_OK(zx_interrupt_create(0, 0, ZX_INTERRUPT_VIRTUAL, intr_.reset_and_get_address()));
+    }
+
+    std::optional<ddk::MmioBuffer> usb_mmio_;
+    std::optional<ddk::MmioBuffer> phy_mmio_;
+    zx::interrupt intr_;
+};
+
+TEST_F(HciTest, TestReadEndpointNumber) {
+    ddk::MmioView v = usb_mmio_->View(0);
+    regs::EPINFO::Get().FromValue(0x33).WriteTo(&v);
+
+    TUsbHci hci(fake_ddk::kFakeParent,
+                *std::move(usb_mmio_),
+                *std::move(phy_mmio_),
+                intr_.release());
+
+    EXPECT_OK(hci.Init());
+    EXPECT_EQ(3, regs::INDEX::Get().ReadFrom(&v).selected_endpoint());
+    hci.DdkUnbind();
+}
+
+} // namespace mt_usb_hci
+
+int main(int argc, char *argv[]) {
+    return RUN_ALL_TESTS(argc, argv);
+}
diff --git a/zircon/system/dev/usb/mt-musb-host/usb-hci.cpp b/zircon/system/dev/usb/mt-musb-host/usb-hci.cpp
index bae360e..335c6a7 100644
--- a/zircon/system/dev/usb/mt-musb-host/usb-hci.cpp
+++ b/zircon/system/dev/usb/mt-musb-host/usb-hci.cpp
@@ -8,12 +8,14 @@
 #include "usb-root-hub.h"
 #include "usb-spew.h"
 
+#include <algorithm>
 #include <ddk/binding.h>
 #include <ddk/debug.h>
 #include <ddk/device.h>
 #include <ddk/driver.h>
 #include <ddk/platform-defs.h>
 #include <fbl/auto_call.h>
+#include <lib/device-protocol/pdev.h>
 #include <lib/zx/time.h>
 #include <soc/mt8167/mt8167-usb.h>
 #include <soc/mt8167/mt8167-usb-phy.h>
@@ -197,9 +199,9 @@
 }
 
 void UsbHci::DdkUnbind() {
-    if (irq_thread_) {
+    if (irq_thread_.joinable()) {
         irq_.destroy();
-        thrd_join(irq_thread_, nullptr);
+        irq_thread_.join();
     }
 }
 
@@ -214,14 +216,14 @@
     }
 
     auto go = [](void* arg) { return static_cast<UsbHci*>(arg)->IrqThread(); };
-    auto rc = thrd_create_with_name(&irq_thread_, go, this, "usb-hci-irq-thread");
-    if (rc != thrd_success) {
+    irq_thread_ = std::thread(go, this);
+    if (!irq_thread_.joinable()) {
         return ZX_ERR_INTERNAL;
     }
 
     auto cleanup = fbl::MakeAutoCall([&]() {
         irq_.destroy();
-        thrd_join(irq_thread_, nullptr);
+        irq_thread_.join();
     });
 
     status = InitRootHub();
@@ -229,7 +231,7 @@
         return status;
     }
 
-    status = InitFifo();
+    status = InitEndpointControllers();
     if (status != ZX_OK) {
         return status;
     }
@@ -255,7 +257,7 @@
     // See: MUSBMHDRC 13.2 for the order in which IRQ events need to be serviced.
     if (irqs.conn()) HandleConnect();
     if (irqs.discon()) HandleDisconnect();
-    for (uint8_t i=0; i <= kMaxEpNum; i++) {
+    for (uint8_t i = 0; i <= std::max(rx_ep_count_, tx_ep_count_); i++) {
         auto mask = static_cast<uint16_t>(1 << i);
         if ((tx_irqs.ep_tx() & mask) || (rx_irqs.ep_rx() & mask)) {
             // Here, note that each endpoint can either be an IN or OUT-type endpoint, but not both.
@@ -306,7 +308,7 @@
     for (;;) {
         status = irq_.wait(nullptr);
         if (status == ZX_ERR_CANCELED) {
-            zxlogf(TRACE, "error break\n");
+            zxlogf(TRACE, "irq thread break\n");
             break;
         } else if (status != ZX_OK) {
             zxlogf(ERROR, "irq wait error: %s\n", zx_status_get_string(status));
@@ -363,29 +365,34 @@
     return ZX_OK;
 }
 
-zx_status_t UsbHci::InitFifo() {
+zx_status_t UsbHci::InitEndpointControllers() {
+    auto epinfo = regs::EPINFO::Get().ReadFrom(usb_mmio());
+    rx_ep_count_ = epinfo.rxendpoints();
+    tx_ep_count_ = epinfo.txendpoints();
+
     // Each FIFO is initialized to the largest it could possibly be (singly-buffered).  As endpoints
     // are subsequently intialized, each FIFO will be appropriately resized based on the needs of
     // the endpoint the FIFO supports.  Here, note that FIFO assumes 64-bit wordsize.
     constexpr uint32_t fifo_size = kFifoMaxSize >> 3;
     uint32_t fifo_addr = (64 >> 3); // The first 64 bytes are used by endpoint-0.
-    for (uint8_t i = 1; i <= kMaxEpNum; i++) {
+    for (uint8_t i = 1; i <= rx_ep_count_; i++) {
         regs::INDEX::Get().FromValue(0).set_selected_endpoint(i).WriteTo(usb_mmio());
-
-        regs::TXFIFOADD::Get().FromValue(0)
-            .set_txfifoadd(static_cast<uint16_t>(fifo_addr))
-            .WriteTo(usb_mmio());
-        fifo_addr += fifo_size;
-
         regs::RXFIFOADD::Get().FromValue(0)
             .set_rxfifoadd(static_cast<uint16_t>(fifo_addr))
             .WriteTo(usb_mmio());
         fifo_addr += fifo_size;
 
         // See: MUSBMHDRC section 3.10.1.
-        regs::TXFIFOSZ::Get().FromValue(0).set_txsz(0x9).WriteTo(usb_mmio());
         regs::RXFIFOSZ::Get().FromValue(0).set_rxsz(0x9).WriteTo(usb_mmio());
     }
+    for (uint8_t i = 1; i <= tx_ep_count_; i++) {
+        regs::INDEX::Get().FromValue(0).set_selected_endpoint(i).WriteTo(usb_mmio());
+        regs::TXFIFOADD::Get().FromValue(0)
+            .set_txfifoadd(static_cast<uint16_t>(fifo_addr))
+            .WriteTo(usb_mmio());
+        fifo_addr += fifo_size;
+        regs::TXFIFOSZ::Get().FromValue(0).set_txsz(0x9).WriteTo(usb_mmio());
+    }
     return ZX_OK;
 }
 
diff --git a/zircon/system/dev/usb/mt-musb-host/usb-hci.h b/zircon/system/dev/usb/mt-musb-host/usb-hci.h
index d6349fa..c7971ae 100644
--- a/zircon/system/dev/usb/mt-musb-host/usb-hci.h
+++ b/zircon/system/dev/usb/mt-musb-host/usb-hci.h
@@ -9,14 +9,13 @@
 
 #include <array>
 #include <ddktl/device.h>
-#include <lib/device-protocol/pdev.h>
 #include <ddktl/protocol/usb/hci.h>
 #include <ddktl/protocol/usb/bus.h>
 #include <lib/mmio/mmio.h>
 #include <lib/zx/interrupt.h>
 #include <memory>
 #include <optional>
-#include <threads.h>
+#include <thread>
 
 namespace mt_usb_hci {
 
@@ -71,18 +70,19 @@
     zx_status_t UsbHciCancelAll(uint32_t device_id, uint8_t ep_address);
     size_t UsbHciGetRequestSize();
 
+protected:
+    // Initialize the USB HCI.
+    zx_status_t Init();
+
 private:
     ddk::MmioBuffer* usb_mmio() { return &usb_mmio_; }
     ddk::MmioBuffer* phy_mmio() { return &phy_mmio_; }
     UsbRootHub* root_hub() { return static_cast<UsbRootHub*>(device_[kRootHubId].get()); }
 
-    // Initialize the USB HCI.
-    zx_status_t Init();
-
     // Initialize the given USB HCI sub-components.
     zx_status_t InitPhy();
     zx_status_t InitRootHub();
-    zx_status_t InitFifo();
+    zx_status_t InitEndpointControllers();
 
     // Start a USB session.
     void StartSession();
@@ -104,7 +104,7 @@
     zx::interrupt irq_;
 
     // An async. thread responding to USB-common interrupt events.
-    thrd_t irq_thread_;
+    std::thread irq_thread_;
 
     // The USB-bus device, used to announce new physical devices to the upper USB stack.
     ddk::UsbBusInterfaceProtocolClient bus_;
@@ -113,6 +113,10 @@
     // reserved and should not be used.  Additionally, device_[128] is reserved for the logical usb
     // root-hub device.
     std::array<std::unique_ptr<UsbDevice>, kMaxDevices> device_;
+
+    // The count of supported RX/TX endpoints in the design.
+    int rx_ep_count_;
+    int tx_ep_count_;
 };
 
 } // namespace mt_usb_hci
diff --git a/zircon/system/utest/BUILD.gn b/zircon/system/utest/BUILD.gn
index c650712..f8ec0e4 100644
--- a/zircon/system/utest/BUILD.gn
+++ b/zircon/system/utest/BUILD.gn
@@ -75,6 +75,7 @@
       "$zx/system/dev/thermal/aml-thermal-s912:aml-thermal-s912-test",
       "$zx/system/dev/thermal/mtk-thermal:mtk-thermal-test",
       "$zx/system/dev/usb/mt-musb-host:mt-hci-request-queue-test",
+      "$zx/system/dev/usb/mt-musb-host:mt-hci-test",
       "$zx/system/dev/usb/mt-musb-host:mt-hci-transaction-test",
       "$zx/system/uapp/disk-pave:install-disk-image-test",
       "$zx/system/uapp/nand-util:nand-util-test",