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"
8 #include "base/location.h"
9 #include "base/mac/foundation_util.h"
10 #include "base/numerics/safe_math.h"
11 #include "base/single_thread_task_runner.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/thread_task_runner_handle.h"
14 #include "components/device_event_log/device_event_log.h"
15 #include "device/hid/hid_connection_mac.h"
21 std::string
HexErrorCode(IOReturn error_code
) {
22 return base::StringPrintf("0x%04x", error_code
);
27 HidConnectionMac::HidConnectionMac(
28 IOHIDDeviceRef device
,
29 scoped_refptr
<HidDeviceInfo
> device_info
,
30 scoped_refptr
<base::SingleThreadTaskRunner
> file_task_runner
)
31 : HidConnection(device_info
),
32 device_(device
, base::scoped_policy::RETAIN
),
33 file_task_runner_(file_task_runner
) {
34 task_runner_
= base::ThreadTaskRunnerHandle::Get();
35 DCHECK(task_runner_
.get());
37 IOHIDDeviceScheduleWithRunLoop(
38 device_
.get(), CFRunLoopGetMain(), kCFRunLoopDefaultMode
);
40 size_t expected_report_size
= device_info
->max_input_report_size();
41 if (device_info
->has_report_id()) {
42 expected_report_size
++;
44 inbound_buffer_
.resize(expected_report_size
);
46 if (inbound_buffer_
.size() > 0) {
47 AddRef(); // Hold a reference to this while this callback is registered.
48 IOHIDDeviceRegisterInputReportCallback(
51 inbound_buffer_
.size(),
52 &HidConnectionMac::InputReportCallback
,
57 HidConnectionMac::~HidConnectionMac() {
60 void HidConnectionMac::PlatformClose() {
61 if (inbound_buffer_
.size() > 0) {
62 IOHIDDeviceRegisterInputReportCallback(
63 device_
.get(), &inbound_buffer_
[0], inbound_buffer_
.size(), NULL
, this);
64 // Release the reference taken when this callback was registered.
68 IOHIDDeviceUnscheduleFromRunLoop(
69 device_
.get(), CFRunLoopGetMain(), kCFRunLoopDefaultMode
);
70 IOReturn result
= IOHIDDeviceClose(device_
.get(), 0);
71 if (result
!= kIOReturnSuccess
) {
72 HID_LOG(EVENT
) << "Failed to close HID device: " << HexErrorCode(result
);
75 while (!pending_reads_
.empty()) {
76 pending_reads_
.front().callback
.Run(false, NULL
, 0);
81 void HidConnectionMac::PlatformRead(const ReadCallback
& callback
) {
82 DCHECK(thread_checker().CalledOnValidThread());
83 PendingHidRead pending_read
;
84 pending_read
.callback
= callback
;
85 pending_reads_
.push(pending_read
);
89 void HidConnectionMac::PlatformWrite(scoped_refptr
<net::IOBuffer
> buffer
,
91 const WriteCallback
& callback
) {
92 file_task_runner_
->PostTask(FROM_HERE
,
93 base::Bind(&HidConnectionMac::SetReportAsync
,
95 kIOHIDReportTypeOutput
,
101 void HidConnectionMac::PlatformGetFeatureReport(uint8_t report_id
,
102 const ReadCallback
& callback
) {
103 file_task_runner_
->PostTask(
106 &HidConnectionMac::GetFeatureReportAsync
, this, report_id
, callback
));
109 void HidConnectionMac::PlatformSendFeatureReport(
110 scoped_refptr
<net::IOBuffer
> buffer
,
112 const WriteCallback
& callback
) {
113 file_task_runner_
->PostTask(FROM_HERE
,
114 base::Bind(&HidConnectionMac::SetReportAsync
,
116 kIOHIDReportTypeFeature
,
123 void HidConnectionMac::InputReportCallback(void* context
,
126 IOHIDReportType type
,
128 uint8_t* report_bytes
,
129 CFIndex report_length
) {
130 HidConnectionMac
* connection
= static_cast<HidConnectionMac
*>(context
);
131 if (result
!= kIOReturnSuccess
) {
132 HID_LOG(EVENT
) << "Failed to read input report: " << HexErrorCode(result
);
136 scoped_refptr
<net::IOBufferWithSize
> buffer
;
137 if (connection
->device_info()->has_report_id()) {
138 // report_id is already contained in report_bytes
139 buffer
= new net::IOBufferWithSize(
140 base::CheckedNumeric
<size_t>(report_length
).ValueOrDie());
141 memcpy(buffer
->data(), report_bytes
, report_length
);
143 buffer
= new net::IOBufferWithSize(
144 (base::CheckedNumeric
<size_t>(report_length
) + 1).ValueOrDie());
145 buffer
->data()[0] = 0;
146 memcpy(buffer
->data() + 1, report_bytes
, report_length
);
149 connection
->ProcessInputReport(buffer
);
152 void HidConnectionMac::ProcessInputReport(
153 scoped_refptr
<net::IOBufferWithSize
> buffer
) {
154 DCHECK(thread_checker().CalledOnValidThread());
155 PendingHidReport report
;
156 report
.buffer
= buffer
;
157 report
.size
= buffer
->size();
158 pending_reports_
.push(report
);
162 void HidConnectionMac::ProcessReadQueue() {
163 DCHECK(thread_checker().CalledOnValidThread());
164 while (pending_reads_
.size() && pending_reports_
.size()) {
165 PendingHidRead read
= pending_reads_
.front();
166 PendingHidReport report
= pending_reports_
.front();
168 pending_reports_
.pop();
169 if (CompleteRead(report
.buffer
, report
.size
, read
.callback
)) {
170 pending_reads_
.pop();
175 void HidConnectionMac::GetFeatureReportAsync(uint8_t report_id
,
176 const ReadCallback
& callback
) {
177 scoped_refptr
<net::IOBufferWithSize
> buffer(
178 new net::IOBufferWithSize(device_info()->max_feature_report_size() + 1));
179 CFIndex report_size
= buffer
->size();
181 // The IOHIDDevice object is shared with the UI thread and so this function
182 // should probably be called there but it may block and the asynchronous
183 // version is NOT IMPLEMENTED. I've examined the open source implementation
184 // of this function and believe it is a simple enough wrapper around the
185 // kernel API that this is safe.
187 IOHIDDeviceGetReport(device_
.get(),
188 kIOHIDReportTypeFeature
,
190 reinterpret_cast<uint8_t*>(buffer
->data()),
192 if (result
== kIOReturnSuccess
) {
193 task_runner_
->PostTask(
195 base::Bind(&HidConnectionMac::ReturnAsyncResult
,
197 base::Bind(callback
, true, buffer
, report_size
)));
199 HID_LOG(EVENT
) << "Failed to get feature report: " << HexErrorCode(result
);
200 task_runner_
->PostTask(FROM_HERE
,
201 base::Bind(&HidConnectionMac::ReturnAsyncResult
,
203 base::Bind(callback
, false, nullptr, 0)));
207 void HidConnectionMac::SetReportAsync(IOHIDReportType report_type
,
208 scoped_refptr
<net::IOBuffer
> buffer
,
210 const WriteCallback
& callback
) {
211 uint8_t* data
= reinterpret_cast<uint8_t*>(buffer
->data());
213 uint8_t report_id
= data
[0];
214 if (report_id
== 0) {
215 // OS X only expects the first byte of the buffer to be the report ID if the
216 // report ID is non-zero.
221 // The IOHIDDevice object is shared with the UI thread and so this function
222 // should probably be called there but it may block and the asynchronous
223 // version is NOT IMPLEMENTED. I've examined the open source implementation
224 // of this function and believe it is a simple enough wrapper around the
225 // kernel API that this is safe.
227 IOHIDDeviceSetReport(device_
.get(), report_type
, report_id
, data
, size
);
228 if (result
== kIOReturnSuccess
) {
229 task_runner_
->PostTask(FROM_HERE
,
230 base::Bind(&HidConnectionMac::ReturnAsyncResult
,
232 base::Bind(callback
, true)));
234 HID_LOG(EVENT
) << "Failed to set report: " << HexErrorCode(result
);
235 task_runner_
->PostTask(FROM_HERE
,
236 base::Bind(&HidConnectionMac::ReturnAsyncResult
,
238 base::Bind(callback
, false)));
242 void HidConnectionMac::ReturnAsyncResult(const base::Closure
& callback
) {
246 } // namespace device