1/*
2 * Copyright 2020, J��r��me Duval, jerome.duval@gmail.com.
3 * Copyright 2008-2011, Michael Lotz <mmlr@mlotz.ch>
4 * Distributed under the terms of the MIT license.
5 */
6
7
8//!	Driver for I2C Human Interface Devices.
9
10
11#include "Driver.h"
12#include "HIDDevice.h"
13#include "HIDReport.h"
14#include "HIDWriter.h"
15#include "ProtocolHandler.h"
16
17#include <usb/USB_hid.h>
18
19#include <stdlib.h>
20#include <stdio.h>
21#include <string.h>
22#include <unistd.h>
23#include <new>
24
25
26HIDDevice::HIDDevice(uint16 descriptorAddress, i2c_device_interface* i2c,
27	i2c_device i2cCookie)
28	:	fStatus(B_NO_INIT),
29		fTransferLastschedule(0),
30		fTransferScheduled(0),
31		fTransferBufferSize(0),
32		fTransferBuffer(NULL),
33		fOpenCount(0),
34		fRemoved(false),
35		fParser(this),
36		fProtocolHandlerCount(0),
37		fProtocolHandlerList(NULL),
38		fDescriptorAddress(descriptorAddress),
39		fI2C(i2c),
40		fI2CCookie(i2cCookie)
41{
42	// fetch HID descriptor
43	fStatus = _FetchBuffer((uint8*)&fDescriptorAddress,
44		sizeof(fDescriptorAddress), &fDescriptor, sizeof(fDescriptor));
45	if (fStatus != B_OK) {
46		ERROR("failed to fetch HID descriptor\n");
47		return;
48	}
49
50	// fetch HID Report descriptor
51
52	HIDWriter descriptorWriter;
53
54	uint16 descriptorLength = fDescriptor.wReportDescLength;
55	fReportDescriptor = (uint8 *)malloc(descriptorLength);
56	if (fReportDescriptor == NULL) {
57		ERROR("failed to allocate buffer for report descriptor\n");
58		fStatus = B_NO_MEMORY;
59		return;
60	}
61
62	uint16 reportDescRegister = fDescriptor.wReportDescRegister;
63	fStatus = _FetchBuffer((uint8*)&reportDescRegister,
64		sizeof(reportDescRegister), fReportDescriptor,
65		descriptorLength);
66	if (fStatus != B_OK) {
67		ERROR("failed tot get report descriptor\n");
68		free(fReportDescriptor);
69		return;
70	}
71
72#if 1
73	// save report descriptor for troubleshooting
74	char outputFile[128];
75	sprintf(outputFile, "/tmp/i2c_hid_report_descriptor_%04x_%04x.bin",
76		fDescriptor.wVendorID, fDescriptor.wProductID);
77	int fd = open(outputFile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
78	if (fd >= 0) {
79		write(fd, fReportDescriptor, descriptorLength);
80		close(fd);
81	}
82#endif
83
84	status_t result = fParser.ParseReportDescriptor(fReportDescriptor,
85		descriptorLength);
86	free(fReportDescriptor);
87
88	if (result != B_OK) {
89		ERROR("parsing the report descriptor failed\n");
90		fStatus = result;
91		return;
92	}
93
94#if 0
95	for (uint32 i = 0; i < fParser.CountReports(HID_REPORT_TYPE_ANY); i++)
96		fParser.ReportAt(HID_REPORT_TYPE_ANY, i)->PrintToStream();
97#endif
98
99	fTransferBufferSize = fParser.MaxReportSize();
100	if (fTransferBufferSize == 0) {
101		TRACE_ALWAYS("report claims a report size of 0\n");
102		return;
103	}
104
105	// We pad the allocation size so that we can always read 32 bits at a time
106	// (as done in HIDReportItem) without the need for an additional boundary
107	// check. We don't increase the transfer buffer size though as to not expose
108	// this implementation detail onto the device when scheduling transfers.
109	fTransferBuffer = (uint8 *)malloc(fTransferBufferSize + 3);
110	if (fTransferBuffer == NULL) {
111		TRACE_ALWAYS("failed to allocate transfer buffer\n");
112		fStatus = B_NO_MEMORY;
113		return;
114	}
115
116	ProtocolHandler::AddHandlers(*this, fProtocolHandlerList,
117		fProtocolHandlerCount);
118	fStatus = B_OK;
119}
120
121
122HIDDevice::~HIDDevice()
123{
124	ProtocolHandler *handler = fProtocolHandlerList;
125	while (handler != NULL) {
126		ProtocolHandler *next = handler->NextHandler();
127		delete handler;
128		handler = next;
129	}
130
131	free(fTransferBuffer);
132}
133
134
135status_t
136HIDDevice::Open(ProtocolHandler *handler, uint32 flags)
137{
138	atomic_add(&fOpenCount, 1);
139	_Reset();
140
141	return B_OK;
142}
143
144
145status_t
146HIDDevice::Close(ProtocolHandler *handler)
147{
148	atomic_add(&fOpenCount, -1);
149	_SetPower(I2C_HID_POWER_OFF);
150
151	return B_OK;
152}
153
154
155void
156HIDDevice::Removed()
157{
158	fRemoved = true;
159}
160
161
162status_t
163HIDDevice::MaybeScheduleTransfer(HIDReport *report)
164{
165	if (fRemoved)
166		return ENODEV;
167
168	if (atomic_get_and_set(&fTransferScheduled, 1) != 0) {
169		// someone else already caused a transfer to be scheduled
170		return B_OK;
171	}
172
173	snooze_until(fTransferLastschedule, B_SYSTEM_TIMEBASE);
174	fTransferLastschedule = system_time() + 10000;
175
176	TRACE("scheduling interrupt transfer of %lu bytes\n",
177		report->ReportSize());
178	return _FetchReport(report->Type(), report->ID(), report->ReportSize());
179}
180
181
182status_t
183HIDDevice::SendReport(HIDReport *report)
184{
185	// TODO
186	return B_OK;
187}
188
189
190ProtocolHandler *
191HIDDevice::ProtocolHandlerAt(uint32 index) const
192{
193	ProtocolHandler *handler = fProtocolHandlerList;
194	while (handler != NULL) {
195		if (index == 0)
196			return handler;
197
198		handler = handler->NextHandler();
199		index--;
200	}
201
202	return NULL;
203}
204
205
206void
207HIDDevice::_UnstallCallback(void *cookie, status_t status, void *data,
208	size_t actualLength)
209{
210	HIDDevice *device = (HIDDevice *)cookie;
211	if (status != B_OK) {
212		TRACE_ALWAYS("Unable to unstall device: %s\n", strerror(status));
213	}
214
215	// Now report the original failure, since we're ready to retry
216	_TransferCallback(cookie, B_ERROR, device->fTransferBuffer, 0);
217}
218
219
220void
221HIDDevice::_TransferCallback(void *cookie, status_t status, void *data,
222	size_t actualLength)
223{
224	HIDDevice *device = (HIDDevice *)cookie;
225
226	atomic_set(&device->fTransferScheduled, 0);
227	device->fParser.SetReport(status, device->fTransferBuffer, actualLength);
228}
229
230
231status_t
232HIDDevice::_Reset()
233{
234	CALLED();
235	status_t status = _SetPower(I2C_HID_POWER_ON);
236	if (status != B_OK)
237		return status;
238
239	snooze(1000);
240
241	uint8 cmd[] = {
242		(uint8)(fDescriptor.wCommandRegister & 0xff),
243		(uint8)(fDescriptor.wCommandRegister >> 8),
244		0,
245		I2C_HID_CMD_RESET,
246	};
247
248	status = _ExecCommand(I2C_OP_WRITE_STOP, cmd, sizeof(cmd), NULL, 0);
249	if (status != B_OK) {
250		_SetPower(I2C_HID_POWER_OFF);
251		return status;
252	}
253
254	snooze(1000);
255	return B_OK;
256}
257
258
259status_t
260HIDDevice::_SetPower(uint8 power)
261{
262	CALLED();
263	uint8 cmd[] = {
264		(uint8)(fDescriptor.wCommandRegister & 0xff),
265		(uint8)(fDescriptor.wCommandRegister >> 8),
266		power,
267		I2C_HID_CMD_SET_POWER
268	};
269
270	return _ExecCommand(I2C_OP_WRITE_STOP, cmd, sizeof(cmd), NULL, 0);
271}
272
273
274status_t
275HIDDevice::_FetchReport(uint8 type, uint8 id, size_t reportSize)
276{
277	uint8 reportId = id > 15 ? 15 : id;
278	size_t cmdLength = 6;
279	uint8 cmd[] = {
280		(uint8)(fDescriptor.wCommandRegister & 0xff),
281		(uint8)(fDescriptor.wCommandRegister >> 8),
282		(uint8)(reportId | (type << 4)),
283		I2C_HID_CMD_GET_REPORT,
284		0, 0, 0,
285	};
286
287	int dataOffset = 4;
288	int reportIdLength = 1;
289	if (reportId == 15) {
290		cmd[dataOffset++] = id;
291		cmdLength++;
292		reportIdLength++;
293	}
294	cmd[dataOffset++] = fDescriptor.wDataRegister & 0xff;
295	cmd[dataOffset++] = fDescriptor.wDataRegister >> 8;
296
297	size_t bufferLength = reportSize + reportIdLength + 2;
298
299	status_t status = _FetchBuffer(cmd, cmdLength, fTransferBuffer,
300		bufferLength);
301	if (status != B_OK) {
302		atomic_set(&fTransferScheduled, 0);
303		return status;
304	}
305
306	uint16 actualLength = fTransferBuffer[0] | (fTransferBuffer[1] << 8);
307	TRACE("_FetchReport %" B_PRIuSIZE " %" B_PRIu16 "\n", reportSize,
308		actualLength);
309	if (actualLength <= 2 || actualLength == 0xffff || bufferLength == 0)
310		actualLength = 0;
311	else
312		actualLength -= 2;
313
314	atomic_set(&fTransferScheduled, 0);
315	fParser.SetReport(status,
316		(uint8*)((addr_t)fTransferBuffer + 2), actualLength);
317	return B_OK;
318}
319
320
321status_t
322HIDDevice::_FetchBuffer(uint8* cmd, size_t cmdLength, void* buffer,
323	size_t bufferLength)
324{
325	return _ExecCommand(I2C_OP_READ_STOP, cmd, cmdLength,
326		buffer, bufferLength);
327}
328
329
330status_t
331HIDDevice::_ExecCommand(i2c_op op, uint8* cmd, size_t cmdLength, void* buffer,
332	size_t bufferLength)
333{
334	status_t status = fI2C->acquire_bus(fI2CCookie);
335	if (status != B_OK)
336		return status;
337	status = fI2C->exec_command(fI2CCookie, I2C_OP_READ_STOP, cmd, cmdLength,
338		buffer, bufferLength);
339	fI2C->release_bus(fI2CCookie);
340	return status;
341}
342