Fix shutdown crash when AudioRendererHosts outlive the ResourceScheduler
[chromium-blink-merge.git] / device / hid / hid_connection_mac.cc
blobde93cc66da34f6b6ca194fd3b20a2dbab0ac3bad
1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "device/hid/hid_connection_mac.h"
7 #include "base/bind.h"
8 #include "base/location.h"
9 #include "base/mac/foundation_util.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/thread_task_runner_handle.h"
13 #include "components/device_event_log/device_event_log.h"
14 #include "device/hid/hid_connection_mac.h"
16 namespace device {
18 namespace {
20 std::string HexErrorCode(IOReturn error_code) {
21 return base::StringPrintf("0x%04x", error_code);
24 } // namespace
26 HidConnectionMac::HidConnectionMac(
27 IOHIDDeviceRef device,
28 scoped_refptr<HidDeviceInfo> device_info,
29 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner)
30 : HidConnection(device_info),
31 device_(device, base::scoped_policy::RETAIN),
32 file_task_runner_(file_task_runner) {
33 task_runner_ = base::ThreadTaskRunnerHandle::Get();
34 DCHECK(task_runner_.get());
36 IOHIDDeviceScheduleWithRunLoop(
37 device_.get(), CFRunLoopGetMain(), kCFRunLoopDefaultMode);
39 size_t expected_report_size = device_info->max_input_report_size();
40 if (device_info->has_report_id()) {
41 expected_report_size++;
43 inbound_buffer_.resize(expected_report_size);
45 if (inbound_buffer_.size() > 0) {
46 AddRef(); // Hold a reference to this while this callback is registered.
47 IOHIDDeviceRegisterInputReportCallback(
48 device_.get(),
49 &inbound_buffer_[0],
50 inbound_buffer_.size(),
51 &HidConnectionMac::InputReportCallback,
52 this);
56 HidConnectionMac::~HidConnectionMac() {
59 void HidConnectionMac::PlatformClose() {
60 if (inbound_buffer_.size() > 0) {
61 IOHIDDeviceRegisterInputReportCallback(
62 device_.get(), &inbound_buffer_[0], inbound_buffer_.size(), NULL, this);
63 // Release the reference taken when this callback was registered.
64 Release();
67 IOHIDDeviceUnscheduleFromRunLoop(
68 device_.get(), CFRunLoopGetMain(), kCFRunLoopDefaultMode);
69 IOReturn result = IOHIDDeviceClose(device_.get(), 0);
70 if (result != kIOReturnSuccess) {
71 HID_LOG(EVENT) << "Failed to close HID device: " << HexErrorCode(result);
74 while (!pending_reads_.empty()) {
75 pending_reads_.front().callback.Run(false, NULL, 0);
76 pending_reads_.pop();
80 void HidConnectionMac::PlatformRead(const ReadCallback& callback) {
81 DCHECK(thread_checker().CalledOnValidThread());
82 PendingHidRead pending_read;
83 pending_read.callback = callback;
84 pending_reads_.push(pending_read);
85 ProcessReadQueue();
88 void HidConnectionMac::PlatformWrite(scoped_refptr<net::IOBuffer> buffer,
89 size_t size,
90 const WriteCallback& callback) {
91 file_task_runner_->PostTask(FROM_HERE,
92 base::Bind(&HidConnectionMac::SetReportAsync,
93 this,
94 kIOHIDReportTypeOutput,
95 buffer,
96 size,
97 callback));
100 void HidConnectionMac::PlatformGetFeatureReport(uint8_t report_id,
101 const ReadCallback& callback) {
102 file_task_runner_->PostTask(
103 FROM_HERE,
104 base::Bind(
105 &HidConnectionMac::GetFeatureReportAsync, this, report_id, callback));
108 void HidConnectionMac::PlatformSendFeatureReport(
109 scoped_refptr<net::IOBuffer> buffer,
110 size_t size,
111 const WriteCallback& callback) {
112 file_task_runner_->PostTask(FROM_HERE,
113 base::Bind(&HidConnectionMac::SetReportAsync,
114 this,
115 kIOHIDReportTypeFeature,
116 buffer,
117 size,
118 callback));
121 // static
122 void HidConnectionMac::InputReportCallback(void* context,
123 IOReturn result,
124 void* sender,
125 IOHIDReportType type,
126 uint32_t report_id,
127 uint8_t* report_bytes,
128 CFIndex report_length) {
129 HidConnectionMac* connection = static_cast<HidConnectionMac*>(context);
130 if (result != kIOReturnSuccess) {
131 HID_LOG(EVENT) << "Failed to read input report: " << HexErrorCode(result);
132 return;
135 scoped_refptr<net::IOBufferWithSize> buffer;
136 if (connection->device_info()->has_report_id()) {
137 // report_id is already contained in report_bytes
138 buffer = new net::IOBufferWithSize(report_length);
139 memcpy(buffer->data(), report_bytes, report_length);
140 } else {
141 buffer = new net::IOBufferWithSize(report_length + 1);
142 buffer->data()[0] = 0;
143 memcpy(buffer->data() + 1, report_bytes, report_length);
146 connection->ProcessInputReport(buffer);
149 void HidConnectionMac::ProcessInputReport(
150 scoped_refptr<net::IOBufferWithSize> buffer) {
151 DCHECK(thread_checker().CalledOnValidThread());
152 PendingHidReport report;
153 report.buffer = buffer;
154 report.size = buffer->size();
155 pending_reports_.push(report);
156 ProcessReadQueue();
159 void HidConnectionMac::ProcessReadQueue() {
160 DCHECK(thread_checker().CalledOnValidThread());
161 while (pending_reads_.size() && pending_reports_.size()) {
162 PendingHidRead read = pending_reads_.front();
163 PendingHidReport report = pending_reports_.front();
165 pending_reports_.pop();
166 if (CompleteRead(report.buffer, report.size, read.callback)) {
167 pending_reads_.pop();
172 void HidConnectionMac::GetFeatureReportAsync(uint8_t report_id,
173 const ReadCallback& callback) {
174 scoped_refptr<net::IOBufferWithSize> buffer(
175 new net::IOBufferWithSize(device_info()->max_feature_report_size() + 1));
176 CFIndex report_size = buffer->size();
178 // The IOHIDDevice object is shared with the UI thread and so this function
179 // should probably be called there but it may block and the asynchronous
180 // version is NOT IMPLEMENTED. I've examined the open source implementation
181 // of this function and believe it is a simple enough wrapper around the
182 // kernel API that this is safe.
183 IOReturn result =
184 IOHIDDeviceGetReport(device_.get(),
185 kIOHIDReportTypeFeature,
186 report_id,
187 reinterpret_cast<uint8_t*>(buffer->data()),
188 &report_size);
189 if (result == kIOReturnSuccess) {
190 task_runner_->PostTask(
191 FROM_HERE,
192 base::Bind(&HidConnectionMac::ReturnAsyncResult,
193 this,
194 base::Bind(callback, true, buffer, report_size)));
195 } else {
196 HID_LOG(EVENT) << "Failed to get feature report: " << HexErrorCode(result);
197 task_runner_->PostTask(FROM_HERE,
198 base::Bind(&HidConnectionMac::ReturnAsyncResult,
199 this,
200 base::Bind(callback, false, nullptr, 0)));
204 void HidConnectionMac::SetReportAsync(IOHIDReportType report_type,
205 scoped_refptr<net::IOBuffer> buffer,
206 size_t size,
207 const WriteCallback& callback) {
208 uint8_t* data = reinterpret_cast<uint8_t*>(buffer->data());
209 DCHECK_GE(size, 1u);
210 uint8_t report_id = data[0];
211 if (report_id == 0) {
212 // OS X only expects the first byte of the buffer to be the report ID if the
213 // report ID is non-zero.
214 ++data;
215 --size;
218 // The IOHIDDevice object is shared with the UI thread and so this function
219 // should probably be called there but it may block and the asynchronous
220 // version is NOT IMPLEMENTED. I've examined the open source implementation
221 // of this function and believe it is a simple enough wrapper around the
222 // kernel API that this is safe.
223 IOReturn result =
224 IOHIDDeviceSetReport(device_.get(), report_type, report_id, data, size);
225 if (result == kIOReturnSuccess) {
226 task_runner_->PostTask(FROM_HERE,
227 base::Bind(&HidConnectionMac::ReturnAsyncResult,
228 this,
229 base::Bind(callback, true)));
230 } else {
231 HID_LOG(EVENT) << "Failed to set report: " << HexErrorCode(result);
232 task_runner_->PostTask(FROM_HERE,
233 base::Bind(&HidConnectionMac::ReturnAsyncResult,
234 this,
235 base::Bind(callback, false)));
239 void HidConnectionMac::ReturnAsyncResult(const base::Closure& callback) {
240 callback.Run();
243 } // namespace device