| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/device/usb/usb_device_handle_mac.h" |
| |
| #include <IOKit/IOCFBundle.h> |
| #include <IOKit/IOCFPlugIn.h> |
| #include <IOKit/IOKitLib.h> |
| #include <IOKit/IOReturn.h> |
| #include <IOKit/IOTypes.h> |
| #include <IOKit/usb/IOUSBLib.h> |
| #include <MacTypes.h> |
| |
| #include <memory> |
| #include <numeric> |
| #include <utility> |
| |
| #include "base/mac/scoped_ioobject.h" |
| #include "base/mac/scoped_ioplugininterface.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "components/device_event_log/device_event_log.h" |
| #include "services/device/public/cpp/usb/usb_utils.h" |
| #include "services/device/usb/usb_device_mac.h" |
| |
| namespace device { |
| |
| struct Transfer { |
| UsbDeviceHandleMac::TransferCallback generic_callback; |
| scoped_refptr<UsbDeviceHandleMac> handle; |
| scoped_refptr<base::RefCountedBytes> buffer; |
| std::vector<uint32_t> packet_lengths; |
| std::vector<IOUSBIsocFrame> frame_list; |
| mojom::UsbTransferType type; |
| UsbDeviceHandleMac::IsochronousTransferCallback isochronous_callback; |
| }; |
| |
| namespace { |
| |
| // This is the bit 7 of the request type. |
| enum class EndpointDirection : uint8_t { kIn = 0x80, kOut = 0x00 }; |
| |
| // These are bits 5 and 6 of the request type. |
| enum class RequestType : uint8_t { |
| kStandard = 0x00, |
| kClass = 0x20, |
| kVendor = 0x40, |
| kReserved = 0x60 |
| }; |
| |
| // These are bits 0 and 1 of the request type. |
| enum class RequestRecipient : uint8_t { |
| kDevice = 0x00, |
| kInterface = 0x01, |
| kEndpoint = 0x02, |
| kOther = 0x03, |
| }; |
| |
| mojom::UsbTransferStatus ConvertTransferStatus(IOReturn status) { |
| switch (status) { |
| // kIOReturnUnderrun can be ignored because the lower-than-expected transfer |
| // size is reported alongside the COMPLETED status. |
| case kIOReturnUnderrun: |
| case kIOReturnSuccess: |
| return mojom::UsbTransferStatus::COMPLETED; |
| case kIOUSBTransactionTimeout: |
| return mojom::UsbTransferStatus::TIMEOUT; |
| case kIOUSBPipeStalled: |
| return mojom::UsbTransferStatus::STALLED; |
| case kIOReturnOverrun: |
| return mojom::UsbTransferStatus::BABBLE; |
| case kIOReturnAborted: |
| return mojom::UsbTransferStatus::CANCELLED; |
| default: |
| return mojom::UsbTransferStatus::TRANSFER_ERROR; |
| } |
| } |
| |
| uint8_t ConvertTransferDirection(mojom::UsbTransferDirection direction) { |
| switch (direction) { |
| case mojom::UsbTransferDirection::INBOUND: |
| return static_cast<uint8_t>(EndpointDirection::kIn); |
| case mojom::UsbTransferDirection::OUTBOUND: |
| return static_cast<uint8_t>(EndpointDirection::kOut); |
| } |
| NOTREACHED(); |
| return 0; |
| } |
| |
| uint8_t CreateRequestType(mojom::UsbTransferDirection direction, |
| mojom::UsbControlTransferType request_type, |
| mojom::UsbControlTransferRecipient recipient) { |
| uint8_t result = ConvertTransferDirection(direction); |
| |
| switch (request_type) { |
| case mojom::UsbControlTransferType::STANDARD: |
| result |= static_cast<uint8_t>(RequestType::kStandard); |
| break; |
| case mojom::UsbControlTransferType::CLASS: |
| result |= static_cast<uint8_t>(RequestType::kClass); |
| break; |
| case mojom::UsbControlTransferType::VENDOR: |
| result |= static_cast<uint8_t>(RequestType::kVendor); |
| break; |
| case mojom::UsbControlTransferType::RESERVED: |
| result |= static_cast<uint8_t>(RequestType::kReserved); |
| break; |
| } |
| |
| switch (recipient) { |
| case mojom::UsbControlTransferRecipient::DEVICE: |
| result |= static_cast<uint8_t>(RequestRecipient::kDevice); |
| break; |
| case mojom::UsbControlTransferRecipient::INTERFACE: |
| result |= static_cast<uint8_t>(RequestRecipient::kInterface); |
| break; |
| case mojom::UsbControlTransferRecipient::ENDPOINT: |
| result |= static_cast<uint8_t>(RequestRecipient::kEndpoint); |
| break; |
| case mojom::UsbControlTransferRecipient::OTHER: |
| result |= static_cast<uint8_t>(RequestRecipient::kOther); |
| break; |
| } |
| |
| return result; |
| } |
| |
| } // namespace |
| |
| UsbDeviceHandleMac::UsbDeviceHandleMac( |
| scoped_refptr<UsbDeviceMac> device, |
| ScopedIOUSBDeviceInterface device_interface) |
| : device_interface_(std::move(device_interface)), |
| device_(std::move(device)) {} |
| |
| scoped_refptr<UsbDevice> UsbDeviceHandleMac::GetDevice() const { |
| return device_; |
| } |
| |
| void UsbDeviceHandleMac::Close() { |
| if (!device_) |
| return; |
| |
| if (device_source_) { |
| CFRunLoopRemoveSource(CFRunLoopGetCurrent(), device_source_.get(), |
| kCFRunLoopDefaultMode); |
| } |
| |
| for (const auto& source : sources_) { |
| CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source.second.get(), |
| kCFRunLoopDefaultMode); |
| } |
| |
| IOReturn kr = (*device_interface_)->USBDeviceClose(device_interface_); |
| if (kr != kIOReturnSuccess) { |
| USB_LOG(DEBUG) << "Failed to close device: " << std::hex << kr; |
| } |
| |
| Clear(); |
| device_->HandleClosed(this); |
| device_ = nullptr; |
| } |
| |
| void UsbDeviceHandleMac::SetConfiguration(int configuration_value, |
| ResultCallback callback) { |
| if (!device_) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| if (!base::IsValueInRangeForNumericType<uint8_t>(configuration_value)) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| Clear(); |
| |
| IOReturn kr = |
| (*device_interface_) |
| ->SetConfiguration(device_interface_, |
| static_cast<uint8_t>(configuration_value)); |
| if (kr != kIOReturnSuccess) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| device_->ActiveConfigurationChanged(configuration_value); |
| |
| std::move(callback).Run(true); |
| } |
| |
| void UsbDeviceHandleMac::ClaimInterface(int interface_number, |
| ResultCallback callback) { |
| if (!device_) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| if (!base::IsValueInRangeForNumericType<uint8_t>(interface_number)) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| IOUSBFindInterfaceRequest request; |
| request.bInterfaceClass = kIOUSBFindInterfaceDontCare; |
| request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare; |
| request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; |
| request.bAlternateSetting = kIOUSBFindInterfaceDontCare; |
| |
| base::mac::ScopedIOObject<io_iterator_t> interface_iterator; |
| IOReturn kr = |
| (*device_interface_) |
| ->CreateInterfaceIterator(device_interface_, &request, |
| interface_iterator.InitializeInto()); |
| if (kr != kIOReturnSuccess) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| base::mac::ScopedIOObject<io_service_t> usb_interface; |
| while (usb_interface.reset(IOIteratorNext(interface_iterator)), |
| usb_interface) { |
| base::mac::ScopedIOPluginInterface<IOCFPlugInInterface> plugin_interface; |
| int32_t score; |
| kr = IOCreatePlugInInterfaceForService( |
| usb_interface, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, |
| plugin_interface.InitializeInto(), &score); |
| |
| if (kr != kIOReturnSuccess || !plugin_interface) { |
| USB_LOG(ERROR) << "Unable to create a plug-in: " << std::hex << kr; |
| continue; |
| } |
| |
| ScopedIOUSBInterfaceInterface interface_interface; |
| kr = (*plugin_interface) |
| ->QueryInterface(plugin_interface.get(), |
| CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID), |
| reinterpret_cast<LPVOID*>( |
| interface_interface.InitializeInto())); |
| if (kr != kIOReturnSuccess || !interface_interface) { |
| USB_LOG(ERROR) << "Could not create a device interface: " << std::hex |
| << kr; |
| continue; |
| } |
| |
| uint8_t retrieved_interface_number; |
| kr = (*interface_interface) |
| ->GetInterfaceNumber(interface_interface, |
| &retrieved_interface_number); |
| if (kr != kIOReturnSuccess) { |
| USB_LOG(ERROR) << "Could not retrieve an interface number: " << std::hex |
| << kr; |
| continue; |
| } |
| |
| if (retrieved_interface_number != interface_number) |
| continue; |
| |
| kr = (*interface_interface)->USBInterfaceOpen(interface_interface); |
| if (kr != kIOReturnSuccess) { |
| USB_LOG(ERROR) << "Could not open interface: " << std::hex << kr; |
| break; |
| } |
| |
| interfaces_[interface_number] = interface_interface; |
| base::ScopedCFTypeRef<CFRunLoopSourceRef> run_loop_source; |
| kr = (*interface_interface) |
| ->CreateInterfaceAsyncEventSource( |
| interface_interface, run_loop_source.InitializeInto()); |
| if (kr != kIOReturnSuccess) { |
| USB_LOG(ERROR) << "Could not retrieve port: " << std::hex << kr; |
| (*interface_interface)->USBInterfaceClose(interface_interface); |
| break; |
| } |
| RefreshEndpointMap(); |
| CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source.get(), |
| kCFRunLoopDefaultMode); |
| sources_[interface_number] = run_loop_source; |
| std::move(callback).Run(true); |
| return; |
| } |
| std::move(callback).Run(false); |
| USB_LOG(ERROR) << "Could not find interface matching number: " |
| << interface_number; |
| } |
| |
| void UsbDeviceHandleMac::ReleaseInterface(int interface_number, |
| ResultCallback callback) { |
| if (!device_) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| auto interface_it = interfaces_.find(static_cast<uint8_t>(interface_number)); |
| if (interface_it == interfaces_.end()) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| auto released_interface = std::move(interface_it->second); |
| interfaces_.erase(interface_it); |
| |
| auto source_it = sources_.find(interface_number); |
| if (source_it != sources_.end()) { |
| CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source_it->second.get(), |
| kCFRunLoopDefaultMode); |
| sources_.erase(source_it); |
| } |
| |
| IOReturn kr = (*released_interface)->USBInterfaceClose(released_interface); |
| if (kr != kIOReturnSuccess) { |
| std::move(callback).Run(false); |
| return; |
| } |
| RefreshEndpointMap(); |
| std::move(callback).Run(true); |
| } |
| |
| void UsbDeviceHandleMac::SetInterfaceAlternateSetting(int interface_number, |
| int alternate_setting, |
| ResultCallback callback) { |
| if (!device_) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| auto interface_it = interfaces_.find(interface_number); |
| if (interface_it == interfaces_.end()) { |
| std::move(callback).Run(false); |
| return; |
| } |
| const auto& interface_interface = interface_it->second; |
| |
| IOReturn kr = |
| (*interface_interface) |
| ->SetAlternateInterface(interface_interface, alternate_setting); |
| if (kr != kIOReturnSuccess) { |
| std::move(callback).Run(false); |
| return; |
| } |
| RefreshEndpointMap(); |
| std::move(callback).Run(true); |
| } |
| |
| void UsbDeviceHandleMac::ResetDevice(ResultCallback callback) { |
| if (!device_) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| // TODO(https://crbug.com/1096743): Figure out if open interfaces need to be |
| // closed as well. |
| IOReturn kr = (*device_interface_) |
| ->USBDeviceReEnumerate(device_interface_, /*options=*/0); |
| if (kr != kIOReturnSuccess) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| Clear(); |
| std::move(callback).Run(true); |
| } |
| |
| void UsbDeviceHandleMac::ClearHalt(mojom::UsbTransferDirection direction, |
| uint8_t endpoint_number, |
| ResultCallback callback) { |
| if (!device_) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| uint8_t endpoint_address = |
| ConvertTransferDirection(direction) | endpoint_number; |
| auto* mojom_interface = FindInterfaceByEndpoint(endpoint_address); |
| uint8_t interface_number = mojom_interface->interface_number; |
| |
| auto interface_it = interfaces_.find(interface_number); |
| if (interface_it == interfaces_.end()) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| const auto endpoint_it = endpoint_map_.find(endpoint_address); |
| if (endpoint_it == endpoint_map_.end()) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| const auto& interface_interface = interface_it->second; |
| IOReturn kr = (*interface_interface) |
| ->ClearPipeStall(interface_interface, |
| endpoint_it->second.pipe_reference); |
| if (kr != kIOReturnSuccess) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| std::move(callback).Run(true); |
| } |
| |
| void UsbDeviceHandleMac::ControlTransfer( |
| mojom::UsbTransferDirection direction, |
| mojom::UsbControlTransferType request_type, |
| mojom::UsbControlTransferRecipient recipient, |
| uint8_t request, |
| uint16_t value, |
| uint16_t index, |
| scoped_refptr<base::RefCountedBytes> buffer, |
| unsigned int timeout, |
| TransferCallback callback) { |
| if (!device_) { |
| std::move(callback).Run(mojom::UsbTransferStatus::DISCONNECT, |
| std::move(buffer), 0); |
| return; |
| } |
| |
| if (!base::IsValueInRangeForNumericType<uint16_t>(buffer->size())) { |
| USB_LOG(ERROR) << "Transfer too long."; |
| std::move(callback).Run(mojom::UsbTransferStatus::TRANSFER_ERROR, |
| std::move(buffer), 0); |
| return; |
| } |
| |
| if (!device_source_) { |
| IOReturn kr = (*device_interface_) |
| ->CreateDeviceAsyncEventSource( |
| device_interface_, device_source_.InitializeInto()); |
| if (kr != kIOReturnSuccess) { |
| USB_LOG(ERROR) << "Unable to create device async event source: " |
| << std::hex << kr; |
| std::move(callback).Run(mojom::UsbTransferStatus::TRANSFER_ERROR, |
| std::move(buffer), 0); |
| } |
| CFRunLoopAddSource(CFRunLoopGetCurrent(), device_source_.get(), |
| kCFRunLoopDefaultMode); |
| } |
| |
| IOUSBDevRequestTO device_request; |
| device_request.bRequest = request; |
| device_request.wValue = value; |
| device_request.wIndex = index; |
| device_request.bmRequestType = |
| CreateRequestType(direction, request_type, recipient); |
| device_request.pData = buffer->front_as<void*>(); |
| device_request.wLength = static_cast<uint16_t>(buffer->size()); |
| device_request.completionTimeout = timeout; |
| device_request.noDataTimeout = timeout; |
| |
| auto transfer = std::make_unique<Transfer>(); |
| transfer->generic_callback = std::move(callback); |
| transfer->handle = this; |
| transfer->buffer = std::move(buffer); |
| |
| Transfer* transfer_ptr = transfer.get(); |
| auto result = transfers_.insert(std::move(transfer)); |
| IOReturn kr = (*device_interface_) |
| ->DeviceRequestAsyncTO( |
| device_interface_, &device_request, &AsyncIoCallback, |
| reinterpret_cast<void*>(transfer_ptr)); |
| |
| if (kr != kIOReturnSuccess) { |
| USB_LOG(ERROR) << "Failed to send control request: " << std::hex << kr; |
| std::move((*result.first)->generic_callback) |
| .Run(mojom::UsbTransferStatus::TRANSFER_ERROR, |
| std::move((*result.first)->buffer), 0); |
| transfers_.erase(result.first); |
| } |
| } |
| |
| void UsbDeviceHandleMac::IsochronousTransferIn( |
| uint8_t endpoint, |
| const std::vector<uint32_t>& packet_lengths, |
| unsigned int timeout, |
| IsochronousTransferCallback callback) { |
| if (!device_) { |
| ReportIsochronousTransferError(std::move(callback), packet_lengths, |
| mojom::UsbTransferStatus::DISCONNECT); |
| return; |
| } |
| |
| uint8_t endpoint_address = |
| ConvertTransferDirection(mojom::UsbTransferDirection::INBOUND) | endpoint; |
| const auto endpoint_it = endpoint_map_.find(endpoint_address); |
| if (endpoint_it == endpoint_map_.end()) { |
| USB_LOG(ERROR) << "Failed to submit transfer because endpoint " |
| << int{endpoint_address} |
| << " is not part of a claimed interface."; |
| ReportIsochronousTransferError(std::move(callback), packet_lengths, |
| mojom::UsbTransferStatus::TRANSFER_ERROR); |
| return; |
| } |
| |
| size_t length = |
| std::accumulate(packet_lengths.begin(), packet_lengths.end(), 0u); |
| auto buffer = base::MakeRefCounted<base::RefCountedBytes>(length); |
| |
| auto interface_it = |
| interfaces_.find(endpoint_it->second.interface->interface_number); |
| if (interface_it == interfaces_.end()) { |
| ReportIsochronousTransferError(std::move(callback), packet_lengths, |
| mojom::UsbTransferStatus::TRANSFER_ERROR); |
| return; |
| } |
| const auto& interface_interface = interface_it->second; |
| |
| uint64_t bus_frame; |
| AbsoluteTime time; |
| IOReturn kr = (*interface_interface) |
| ->GetBusFrameNumber(interface_interface, &bus_frame, &time); |
| if (kr != kIOReturnSuccess) { |
| ReportIsochronousTransferError(std::move(callback), packet_lengths, |
| mojom::UsbTransferStatus::TRANSFER_ERROR); |
| return; |
| } |
| |
| auto transfer = std::make_unique<Transfer>(); |
| transfer->isochronous_callback = std::move(callback); |
| transfer->handle = this; |
| transfer->buffer = buffer; |
| transfer->type = mojom::UsbTransferType::ISOCHRONOUS; |
| |
| Transfer* transfer_data = transfer.get(); |
| auto result = transfers_.insert(std::move(transfer)); |
| |
| std::vector<IOUSBIsocFrame> frame_list; |
| for (const auto& size : packet_lengths) { |
| if (!base::IsValueInRangeForNumericType<uint16_t>(size)) { |
| USB_LOG(ERROR) << "Transfer too long."; |
| ReportIsochronousTransferError( |
| std::move(transfer_data->isochronous_callback), packet_lengths, |
| mojom::UsbTransferStatus::TRANSFER_ERROR); |
| return; |
| } |
| IOUSBIsocFrame frame_entry; |
| frame_entry.frReqCount = static_cast<uint16_t>(size); |
| frame_list.push_back(frame_entry); |
| } |
| transfer_data->frame_list = frame_list; |
| |
| kr = (*interface_interface) |
| ->ReadIsochPipeAsync(interface_interface, |
| endpoint_it->second.pipe_reference, |
| buffer->front_as<void*>(), bus_frame, |
| static_cast<uint32_t>(packet_lengths.size()), |
| transfer->frame_list.data(), &AsyncIoCallback, |
| reinterpret_cast<void*>(transfer_data)); |
| |
| if (kr != kIOReturnSuccess) { |
| USB_LOG(ERROR) << "Isochrnous read failed."; |
| ReportIsochronousTransferError( |
| std::move((*result.first)->isochronous_callback), packet_lengths, |
| mojom::UsbTransferStatus::TRANSFER_ERROR); |
| transfers_.erase(result.first); |
| return; |
| } |
| } |
| |
| void UsbDeviceHandleMac::IsochronousTransferOut( |
| uint8_t endpoint, |
| scoped_refptr<base::RefCountedBytes> buffer, |
| const std::vector<uint32_t>& packet_lengths, |
| unsigned int timeout, |
| IsochronousTransferCallback callback) { |
| if (!device_) { |
| ReportIsochronousTransferError(std::move(callback), packet_lengths, |
| mojom::UsbTransferStatus::DISCONNECT); |
| return; |
| } |
| |
| uint8_t endpoint_address = |
| ConvertTransferDirection(mojom::UsbTransferDirection::INBOUND) | endpoint; |
| const auto endpoint_it = endpoint_map_.find(endpoint_address); |
| if (endpoint_it == endpoint_map_.end()) { |
| USB_LOG(ERROR) << "Failed to submit transfer because endpoint " |
| << int{endpoint_address} |
| << " is not part of a claimed interface."; |
| ReportIsochronousTransferError(std::move(callback), packet_lengths, |
| mojom::UsbTransferStatus::TRANSFER_ERROR); |
| return; |
| } |
| |
| auto interface_it = |
| interfaces_.find(endpoint_it->second.interface->interface_number); |
| if (interface_it == interfaces_.end()) { |
| ReportIsochronousTransferError(std::move(callback), packet_lengths, |
| mojom::UsbTransferStatus::TRANSFER_ERROR); |
| return; |
| } |
| const auto& interface_interface = interface_it->second; |
| |
| uint64_t bus_frame; |
| AbsoluteTime time; |
| IOReturn kr = (*interface_interface) |
| ->GetBusFrameNumber(interface_interface, &bus_frame, &time); |
| if (kr != kIOReturnSuccess) { |
| ReportIsochronousTransferError(std::move(callback), packet_lengths, |
| mojom::UsbTransferStatus::TRANSFER_ERROR); |
| return; |
| } |
| |
| auto transfer = std::make_unique<Transfer>(); |
| transfer->isochronous_callback = std::move(callback); |
| transfer->handle = this; |
| transfer->buffer = buffer; |
| transfer->type = mojom::UsbTransferType::ISOCHRONOUS; |
| |
| Transfer* transfer_data = transfer.get(); |
| auto result = transfers_.insert(std::move(transfer)); |
| |
| std::vector<IOUSBIsocFrame> frame_list; |
| for (const auto& size : packet_lengths) { |
| if (!base::IsValueInRangeForNumericType<uint16_t>(size)) { |
| USB_LOG(ERROR) << "Transfer too long."; |
| ReportIsochronousTransferError( |
| std::move(transfer_data->isochronous_callback), packet_lengths, |
| mojom::UsbTransferStatus::TRANSFER_ERROR); |
| return; |
| } |
| IOUSBIsocFrame frame_entry; |
| frame_entry.frReqCount = static_cast<uint16_t>(size); |
| frame_list.push_back(frame_entry); |
| } |
| transfer_data->frame_list = frame_list; |
| |
| kr = (*interface_interface) |
| ->WriteIsochPipeAsync(interface_interface, |
| endpoint_it->second.pipe_reference, |
| buffer->front_as<void*>(), bus_frame, |
| static_cast<uint32_t>(packet_lengths.size()), |
| transfer->frame_list.data(), &AsyncIoCallback, |
| reinterpret_cast<void*>(transfer_data)); |
| |
| if (kr != kIOReturnSuccess) { |
| USB_LOG(ERROR) << "Isochrnous write failed."; |
| ReportIsochronousTransferError( |
| std::move((*result.first)->isochronous_callback), packet_lengths, |
| mojom::UsbTransferStatus::TRANSFER_ERROR); |
| transfers_.erase(result.first); |
| } |
| } |
| |
| void UsbDeviceHandleMac::GenericTransfer( |
| mojom::UsbTransferDirection direction, |
| uint8_t endpoint_number, |
| scoped_refptr<base::RefCountedBytes> buffer, |
| unsigned int timeout, |
| TransferCallback callback) { |
| if (!device_) { |
| std::move(callback).Run(mojom::UsbTransferStatus::DISCONNECT, buffer, 0); |
| return; |
| } |
| |
| uint8_t endpoint_address = |
| ConvertEndpointNumberToAddress(endpoint_number, direction); |
| |
| const auto endpoint_it = endpoint_map_.find(endpoint_address); |
| if (endpoint_it == endpoint_map_.end()) { |
| USB_LOG(ERROR) << "Failed to submit transfer because endpoint " |
| << int{endpoint_address} |
| << " is not part of a claimed interface."; |
| std::move(callback).Run(mojom::UsbTransferStatus::TRANSFER_ERROR, buffer, |
| 0); |
| return; |
| } |
| |
| if (!base::IsValueInRangeForNumericType<uint32_t>(buffer->size())) { |
| USB_LOG(ERROR) << "Transfer too long."; |
| std::move(callback).Run(mojom::UsbTransferStatus::TRANSFER_ERROR, buffer, |
| 0); |
| return; |
| } |
| |
| auto interface_it = |
| interfaces_.find(endpoint_it->second.interface->interface_number); |
| if (interface_it == interfaces_.end()) { |
| std::move(callback).Run(mojom::UsbTransferStatus::TRANSFER_ERROR, buffer, |
| 0); |
| return; |
| } |
| const auto& interface_interface = interface_it->second; |
| |
| auto transfer = std::make_unique<Transfer>(); |
| transfer->generic_callback = std::move(callback); |
| transfer->handle = this; |
| transfer->buffer = buffer; |
| |
| mojom::UsbTransferType transfer_type = endpoint_it->second.endpoint->type; |
| transfer->type = transfer_type; |
| |
| switch (transfer_type) { |
| case mojom::UsbTransferType::BULK: |
| switch (direction) { |
| case mojom::UsbTransferDirection::INBOUND: |
| BulkIn(std::move(interface_interface), |
| endpoint_it->second.pipe_reference, buffer, |
| static_cast<uint32_t>(timeout), std::move(transfer)); |
| return; |
| case mojom::UsbTransferDirection::OUTBOUND: |
| BulkOut(std::move(interface_interface), |
| endpoint_it->second.pipe_reference, buffer, |
| static_cast<uint32_t>(timeout), std::move(transfer)); |
| return; |
| } |
| case mojom::UsbTransferType::INTERRUPT: |
| switch (direction) { |
| case mojom::UsbTransferDirection::INBOUND: |
| InterruptIn(interface_interface, endpoint_it->second.pipe_reference, |
| buffer, std::move(transfer)); |
| return; |
| case mojom::UsbTransferDirection::OUTBOUND: |
| InterruptOut(interface_interface, endpoint_it->second.pipe_reference, |
| buffer, std::move(transfer)); |
| return; |
| } |
| default: |
| std::move(transfer->generic_callback) |
| .Run(mojom::UsbTransferStatus::TRANSFER_ERROR, buffer, 0); |
| } |
| } |
| |
| const mojom::UsbInterfaceInfo* UsbDeviceHandleMac::FindInterfaceByEndpoint( |
| uint8_t endpoint_address) { |
| const auto endpoint_it = endpoint_map_.find(endpoint_address); |
| if (endpoint_it != endpoint_map_.end()) |
| return endpoint_it->second.interface; |
| return nullptr; |
| } |
| |
| UsbDeviceHandleMac::~UsbDeviceHandleMac() {} |
| |
| void UsbDeviceHandleMac::BulkIn( |
| const ScopedIOUSBInterfaceInterface& interface_interface, |
| uint8_t pipe_reference, |
| scoped_refptr<base::RefCountedBytes> buffer, |
| uint32_t timeout, |
| std::unique_ptr<Transfer> transfer) { |
| Transfer* transfer_data = transfer.get(); |
| auto result = transfers_.insert(std::move(transfer)); |
| IOReturn kr = (*interface_interface) |
| ->ReadPipeAsyncTO(interface_interface, pipe_reference, |
| buffer->front_as<void*>(), |
| static_cast<uint32_t>(buffer->size()), |
| timeout, timeout, &AsyncIoCallback, |
| reinterpret_cast<void*>(transfer_data)); |
| |
| if (kr != kIOReturnSuccess) { |
| USB_LOG(ERROR) << "Failed to read from device: " << std::hex << kr; |
| std::move((*result.first)->generic_callback) |
| .Run(mojom::UsbTransferStatus::TRANSFER_ERROR, buffer, 0); |
| transfers_.erase(result.first); |
| } |
| } |
| |
| void UsbDeviceHandleMac::BulkOut( |
| const ScopedIOUSBInterfaceInterface& interface_interface, |
| uint8_t pipe_reference, |
| scoped_refptr<base::RefCountedBytes> buffer, |
| uint32_t timeout, |
| std::unique_ptr<Transfer> transfer) { |
| Transfer* transfer_data = transfer.get(); |
| auto result = transfers_.insert(std::move(transfer)); |
| IOReturn kr = (*interface_interface) |
| ->WritePipeAsyncTO(interface_interface, pipe_reference, |
| buffer->front_as<void*>(), |
| static_cast<uint32_t>(buffer->size()), |
| timeout, timeout, &AsyncIoCallback, |
| reinterpret_cast<void*>(transfer_data)); |
| |
| if (kr != kIOReturnSuccess) { |
| USB_LOG(ERROR) << "Failed to write to device: " << std::hex << kr; |
| std::move((*result.first)->generic_callback) |
| .Run(mojom::UsbTransferStatus::TRANSFER_ERROR, buffer, 0); |
| transfers_.erase(result.first); |
| } |
| } |
| |
| void UsbDeviceHandleMac::InterruptIn( |
| const ScopedIOUSBInterfaceInterface& interface_interface, |
| uint8_t pipe_reference, |
| scoped_refptr<base::RefCountedBytes> buffer, |
| std::unique_ptr<Transfer> transfer) { |
| Transfer* transfer_data = transfer.get(); |
| auto result = transfers_.insert(std::move(transfer)); |
| IOReturn kr = (*interface_interface) |
| ->ReadPipeAsync(interface_interface, pipe_reference, |
| buffer->front_as<void*>(), |
| static_cast<uint32_t>(buffer->size()), |
| &AsyncIoCallback, |
| reinterpret_cast<void*>(transfer_data)); |
| if (kr != kIOReturnSuccess) { |
| USB_LOG(ERROR) << "Failed to read from device: " << std::hex << kr; |
| std::move(transfer_data->generic_callback) |
| .Run(mojom::UsbTransferStatus::TRANSFER_ERROR, buffer, 0); |
| transfers_.erase(result.first); |
| } |
| } |
| |
| void UsbDeviceHandleMac::InterruptOut( |
| const ScopedIOUSBInterfaceInterface& interface_interface, |
| uint8_t pipe_reference, |
| scoped_refptr<base::RefCountedBytes> buffer, |
| std::unique_ptr<Transfer> transfer) { |
| Transfer* transfer_data = transfer.get(); |
| auto result = transfers_.insert(std::move(transfer)); |
| IOReturn kr = (*interface_interface) |
| ->WritePipeAsync(interface_interface, pipe_reference, |
| buffer->front_as<void*>(), |
| static_cast<uint32_t>(buffer->size()), |
| &AsyncIoCallback, |
| reinterpret_cast<void*>(transfer_data)); |
| if (kr != kIOReturnSuccess) { |
| USB_LOG(ERROR) << "Failed to write to device: " << std::hex << kr; |
| std::move(transfer_data->generic_callback) |
| .Run(mojom::UsbTransferStatus::TRANSFER_ERROR, buffer, 0); |
| transfers_.erase(result.first); |
| } |
| } |
| |
| void UsbDeviceHandleMac::RefreshEndpointMap() { |
| endpoint_map_.clear(); |
| const mojom::UsbConfigurationInfo* config = device_->GetActiveConfiguration(); |
| if (!config) |
| return; |
| |
| for (const auto& map_entry : interfaces_) { |
| uint8_t alternate_setting; |
| IOReturn kr = |
| (*map_entry.second) |
| ->GetAlternateSetting(map_entry.second, &alternate_setting); |
| if (kr != kIOReturnSuccess) |
| continue; |
| CombinedInterfaceInfo interface_info = |
| FindInterfaceInfoFromConfig(config, map_entry.first, alternate_setting); |
| |
| if (!interface_info.IsValid()) |
| continue; |
| |
| // macOS references an interface's endpoint via an index number of the |
| // endpoint we want in the given interface. It is called a pipe reference. |
| // The indices start at 1 for each interface. |
| uint8_t pipe_reference = 1; |
| for (const auto& endpoint : interface_info.alternate->endpoints) { |
| endpoint_map_[ConvertEndpointNumberToAddress(*endpoint)] = { |
| interface_info.interface, endpoint.get(), pipe_reference}; |
| pipe_reference++; |
| } |
| } |
| } |
| |
| void UsbDeviceHandleMac::ReportIsochronousTransferError( |
| UsbDeviceHandle::IsochronousTransferCallback callback, |
| std::vector<uint32_t> packet_lengths, |
| mojom::UsbTransferStatus status) { |
| std::vector<mojom::UsbIsochronousPacketPtr> packets; |
| packets.reserve(packet_lengths.size()); |
| for (const auto& packet_length : packet_lengths) { |
| auto packet = mojom::UsbIsochronousPacket::New(); |
| packet->length = packet_length; |
| packet->transferred_length = 0; |
| packet->status = status; |
| packets.push_back(std::move(packet)); |
| } |
| std::move(callback).Run(nullptr, std::move(packets)); |
| } |
| |
| void UsbDeviceHandleMac::Clear() { |
| base::flat_set<std::unique_ptr<Transfer>, base::UniquePtrComparator> |
| transfers; |
| transfers.swap(transfers_); |
| for (auto& transfer : transfers) { |
| DCHECK(transfer); |
| if (transfer->type == mojom::UsbTransferType::ISOCHRONOUS) { |
| ReportIsochronousTransferError(std::move(transfer->isochronous_callback), |
| transfer->packet_lengths, |
| mojom::UsbTransferStatus::TRANSFER_ERROR); |
| } else { |
| std::move(transfer->generic_callback) |
| .Run(mojom::UsbTransferStatus::TRANSFER_ERROR, |
| std::move(transfer->buffer), 0); |
| } |
| } |
| transfers.clear(); |
| interfaces_.clear(); |
| sources_.clear(); |
| } |
| |
| void UsbDeviceHandleMac::OnAsyncGeneric(IOReturn result, |
| size_t size, |
| Transfer* transfer) { |
| auto transfer_it = transfers_.find(transfer); |
| if (transfer_it == transfers_.end()) |
| return; |
| auto transfer_ptr = std::move(*transfer_it); |
| |
| std::move(transfer_ptr->generic_callback) |
| .Run(mojom::UsbTransferStatus::COMPLETED, transfer_ptr->buffer, |
| transfer_ptr->buffer->size()); |
| transfers_.erase(transfer_it); |
| } |
| |
| void UsbDeviceHandleMac::OnAsyncIsochronous(IOReturn result, |
| size_t size, |
| Transfer* transfer) { |
| auto transfer_it = transfers_.find(transfer); |
| if (transfer_it == transfers_.end()) |
| return; |
| auto transfer_ptr = std::move(*transfer_it); |
| |
| std::vector<mojom::UsbIsochronousPacketPtr> packets; |
| packets.reserve(transfer_ptr->frame_list.size()); |
| for (const auto& frame : transfer_ptr->frame_list) { |
| auto packet = mojom::UsbIsochronousPacket::New(); |
| packet->length = frame.frReqCount; |
| packet->transferred_length = frame.frActCount; |
| packet->status = ConvertTransferStatus(frame.frStatus); |
| packets.push_back(std::move(packet)); |
| } |
| |
| std::move(transfer_ptr->isochronous_callback) |
| .Run(transfer_ptr->buffer, std::move(packets)); |
| transfers_.erase(transfer_it); |
| } |
| |
| // static |
| void UsbDeviceHandleMac::AsyncIoCallback(void* refcon, |
| IOReturn result, |
| void* arg0) { |
| auto* transfer = reinterpret_cast<Transfer*>(refcon); |
| DCHECK(transfer); |
| DCHECK(transfer->handle); |
| if (transfer->type == mojom::UsbTransferType::ISOCHRONOUS) { |
| transfer->handle->OnAsyncIsochronous(result, reinterpret_cast<size_t>(arg0), |
| transfer); |
| return; |
| } |
| transfer->handle->OnAsyncGeneric(result, reinterpret_cast<size_t>(arg0), |
| transfer); |
| } |
| |
| } // namespace device |