| // 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-request-queue.h" |
| |
| #include <lib/zx/time.h> |
| #include <zircon/hw/usb.h> |
| #include <zircon/status.h> |
| |
| #include <ddk/debug.h> |
| #include <fbl/auto_lock.h> |
| |
| #include "trace.h" |
| #include "usb-spew.h" |
| |
| namespace mt_usb_hci { |
| |
| zx_status_t TransactionQueue::QueueRequest(usb::BorrowedRequest<> req) { |
| fbl::AutoLock _(&pending_lock_); |
| |
| // To prevent a race condition by which a request is enqueued after having stopped the |
| // processing thread (thus orphaning the request), this check must be made with the lock held. |
| if (halted_.load()) { |
| req.Complete(ZX_ERR_IO_NOT_PRESENT, 0); |
| return ZX_OK; |
| } |
| |
| pending_.push(std::move(req)); |
| pending_cond_.Signal(); |
| return ZX_OK; |
| } |
| |
| zx_status_t TransactionQueue::StartQueueThread() { |
| auto go = [](void* arg) { return static_cast<TransactionQueue*>(arg)->QueueThread(); }; |
| auto rc = thrd_create_with_name(&pending_thread_, go, this, "usb-endpoint-thread"); |
| if (rc != thrd_success) { |
| return ZX_ERR_INTERNAL; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t TransactionQueue::CancelAll() { |
| fbl::AutoLock _(&pending_lock_); |
| if (transaction_) { |
| transaction_->Cancel(); |
| } |
| |
| while (!pending_.is_empty()) { |
| std::optional<usb::BorrowedRequest<>> req = pending_.pop(); |
| req->Complete(ZX_ERR_CANCELED, 0); |
| } |
| |
| return ZX_OK; |
| } |
| |
| size_t TransactionQueue::GetMaxTransferSize() { return static_cast<size_t>(max_pkt_sz_); } |
| |
| zx_status_t TransactionQueue::Halt() { |
| { |
| fbl::AutoLock _(&pending_lock_); |
| if (transaction_) { |
| transaction_->Cancel(); |
| } |
| |
| halted_ = true; |
| pending_cond_.Signal(); |
| } |
| |
| if (pending_thread_) { |
| int rc; |
| int status = thrd_join(pending_thread_, &rc); |
| if (status != thrd_success) { |
| zxlogf(ERROR, "could not join pending_thread"); |
| return ZX_ERR_INTERNAL; |
| } else if (rc != 0) { |
| zxlogf(ERROR, "pending_thread returned nonzero status: %d", rc); |
| return ZX_ERR_INTERNAL; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| int TransactionQueue::QueueThread() { |
| zx_status_t status; |
| std::optional<usb::BorrowedRequest<>> req; |
| |
| for (;;) { |
| { |
| fbl::AutoLock _(&pending_lock_); |
| if (pending_.is_empty()) { |
| if (halted_.load()) { |
| return 0; |
| } |
| |
| // To prevent deadlock, the halted check must be made both before and after waiting. |
| // The first check ensures that Halt() requests issued as a transaction was being |
| // processed by the body of this loop are serviced. The second check ensures that |
| // Halt() requests issued while waiting are serviced. |
| pending_cond_.Wait(&pending_lock_); |
| |
| if (halted_.load()) { |
| return 0; |
| } |
| } |
| req = pending_.pop(); |
| } |
| |
| // Popping an empty queue is something that should never really happen. |
| ZX_ASSERT(req.has_value()); |
| |
| status = DispatchRequest(std::move(req.value())); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "could not process usb request: %s", zx_status_get_string(status)); |
| } |
| } |
| return 0; |
| } |
| |
| zx_status_t ControlQueue::GetDeviceDescriptor(usb_device_descriptor_t* out) { |
| TRACE(); |
| usb_setup_t req = { |
| // GET_DESCRIPTOR request, see: USB 2.0 spec. section 9.4.3. |
| .bmRequestType = 0x80, .bRequest = 0x6, .wValue = 0x0100, |
| .wIndex = 0, .wLength = sizeof(*out), |
| }; |
| |
| transaction_ = std::make_unique<Control>(ControlType::READ, usb_.View(0), req, out, sizeof(*out), |
| max_pkt_sz_, faddr_); |
| transaction_->Advance(); |
| transaction_->Wait(); |
| |
| if (!transaction_->Ok()) { |
| zxlogf(ERROR, "usb transaction did not complete successfully"); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| max_pkt_sz_ = static_cast<size_t>(out->bMaxPacketSize0); |
| return ZX_OK; |
| } |
| |
| zx_status_t ControlQueue::SetAddress(uint8_t addr) { |
| usb_setup_t req = { |
| // SET_ADDRESS request, see: USB 2.0 spec. section 9.4.6. |
| .bmRequestType = 0, .bRequest = 0x5, .wValue = addr, .wIndex = 0, .wLength = 0, |
| }; |
| |
| transaction_ = std::make_unique<Control>(ControlType::ZERO, usb_.View(0), req, nullptr, 0, |
| max_pkt_sz_, faddr_); |
| transaction_->Advance(); |
| transaction_->Wait(); |
| |
| if (!transaction_->Ok()) { |
| zxlogf(ERROR, "usb transaction did not complete successfully"); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // The USB spec. requires at least a 2ms sleep for device to finish processing new address |
| // (see: USB 2.0 spec. 9.2.6.3). |
| zx::nanosleep(zx::deadline_after(zx::msec(5))); |
| |
| faddr_ = addr; |
| return ZX_OK; |
| } |
| |
| zx_status_t ControlQueue::DispatchRequest(usb::BorrowedRequest<> req) { |
| zx_status_t status; |
| usb_setup_t setup = req.request()->setup; |
| |
| if (setup.wLength == 0) { // See: USB 2.0 spec. section 9.3.5. |
| transaction_ = std::make_unique<Control>(ControlType::ZERO, usb_.View(0), setup, nullptr, 0, |
| max_pkt_sz_, faddr_); |
| } else { |
| void* vmo_addr; |
| status = req.Mmap(&vmo_addr); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "could not map request vmo: %s", zx_status_get_string(status)); |
| req.Complete(status, 0); |
| return status; |
| } |
| |
| size_t size = req.request()->header.length; |
| if ((setup.bmRequestType & USB_DIR_MASK) == USB_DIR_IN) { |
| transaction_ = std::make_unique<Control>(ControlType::READ, usb_.View(0), setup, vmo_addr, |
| size, max_pkt_sz_, faddr_); |
| } else { // USB_DIR_OUT |
| transaction_ = std::make_unique<Control>(ControlType::WRITE, usb_.View(0), setup, vmo_addr, |
| size, max_pkt_sz_, faddr_); |
| } |
| } |
| |
| transaction_->Advance(); |
| transaction_->Wait(); |
| |
| if (halted_.load()) { |
| req.Complete(ZX_ERR_IO_NOT_PRESENT, 0); |
| return ZX_OK; |
| } else if (!transaction_->Ok()) { |
| zxlogf(ERROR, "usb control transfer did not complete successfully"); |
| req.Complete(ZX_ERR_INTERNAL, 0); |
| return ZX_ERR_INTERNAL; |
| } |
| req.Complete(ZX_OK, transaction_->actual()); |
| return ZX_OK; |
| } |
| |
| zx_status_t BulkQueue::DispatchRequest(usb::BorrowedRequest<> req) { |
| void* vmo_addr; |
| auto status = req.Mmap(&vmo_addr); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "could not map request vmo: %s", zx_status_get_string(status)); |
| req.Complete(status, 0); |
| return status; |
| } |
| |
| size_t size = req.request()->header.length; |
| transaction_ = std::make_unique<Bulk>(usb_.View(0), faddr_, vmo_addr, size, descriptor_); |
| transaction_->Advance(); |
| transaction_->Wait(); |
| |
| if (halted_.load()) { |
| req.Complete(ZX_ERR_IO_NOT_PRESENT, 0); |
| return ZX_OK; |
| } else if (!transaction_->Ok()) { |
| zxlogf(ERROR, "usb bulk transfer did not complete successfully"); |
| req.Complete(ZX_ERR_INTERNAL, 0); |
| return ZX_ERR_INTERNAL; |
| } |
| req.Complete(ZX_OK, transaction_->actual()); |
| return ZX_OK; |
| } |
| |
| zx_status_t InterruptQueue::DispatchRequest(usb::BorrowedRequest<> req) { |
| void* vmo_addr; |
| auto status = req.Mmap(&vmo_addr); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "could not map request vmo: %s", zx_status_get_string(status)); |
| req.Complete(status, 0); |
| return status; |
| } |
| |
| size_t size = req.request()->header.length; |
| transaction_ = std::make_unique<Interrupt>(usb_.View(0), faddr_, vmo_addr, size, descriptor_); |
| transaction_->Advance(); |
| transaction_->Wait(); |
| |
| if (halted_.load()) { |
| req.Complete(ZX_ERR_IO_NOT_PRESENT, 0); |
| return ZX_OK; |
| } else if (!transaction_->Ok()) { |
| zxlogf(ERROR, "usb interrupt transfer did not complete successfully"); |
| req.Complete(ZX_ERR_INTERNAL, 0); |
| return ZX_ERR_INTERNAL; |
| } |
| req.Complete(ZX_OK, transaction_->actual()); |
| return ZX_OK; |
| } |
| |
| } // namespace mt_usb_hci |