blob: 588b61254121c797dd929f33a4fd96e07dc3c963 [file] [log] [blame]
Austin Schuh41baf202022-01-01 14:33:40 -08001/*
2 * The MIT License (MIT)
3 *
4 * Copyright (c) 2019 Ha Thach (tinyusb.org)
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 *
24 * This file is part of the TinyUSB stack.
25 */
26
27#include "tusb_option.h"
28
29#if (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_HID)
30
31//--------------------------------------------------------------------+
32// INCLUDE
33//--------------------------------------------------------------------+
34#include "device/usbd.h"
35#include "device/usbd_pvt.h"
36
37#include "hid_device.h"
38
39//--------------------------------------------------------------------+
40// MACRO CONSTANT TYPEDEF
41//--------------------------------------------------------------------+
42typedef struct
43{
44 uint8_t itf_num;
45 uint8_t ep_in;
46 uint8_t ep_out; // optional Out endpoint
47 uint8_t itf_protocol; // Boot mouse or keyboard
48
49 uint8_t protocol_mode; // Boot (0) or Report protocol (1)
50 uint8_t idle_rate; // up to application to handle idle rate
51 uint16_t report_desc_len;
52
53 CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUD_HID_EP_BUFSIZE];
54 CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUD_HID_EP_BUFSIZE];
55
56 // TODO save hid descriptor since host can specifically request this after enumeration
57 // Note: HID descriptor may be not available from application after enumeration
58 tusb_hid_descriptor_hid_t const * hid_descriptor;
59} hidd_interface_t;
60
61CFG_TUSB_MEM_SECTION static hidd_interface_t _hidd_itf[CFG_TUD_HID];
62
63/*------------- Helpers -------------*/
64static inline uint8_t get_index_by_itfnum(uint8_t itf_num)
65{
66 for (uint8_t i=0; i < CFG_TUD_HID; i++ )
67 {
68 if ( itf_num == _hidd_itf[i].itf_num ) return i;
69 }
70
71 return 0xFF;
72}
73
74//--------------------------------------------------------------------+
75// APPLICATION API
76//--------------------------------------------------------------------+
77bool tud_hid_n_ready(uint8_t instance)
78{
79 uint8_t const ep_in = _hidd_itf[instance].ep_in;
80 return tud_ready() && (ep_in != 0) && !usbd_edpt_busy(TUD_OPT_RHPORT, ep_in);
81}
82
83bool tud_hid_n_report(uint8_t instance, uint8_t report_id, void const* report, uint8_t len)
84{
85 uint8_t const rhport = 0;
86 hidd_interface_t * p_hid = &_hidd_itf[instance];
87
88 // claim endpoint
89 TU_VERIFY( usbd_edpt_claim(rhport, p_hid->ep_in) );
90
91 // prepare data
92 if (report_id)
93 {
94 len = tu_min8(len, CFG_TUD_HID_EP_BUFSIZE-1);
95
96 p_hid->epin_buf[0] = report_id;
97 memcpy(p_hid->epin_buf+1, report, len);
98 len++;
99 }else
100 {
101 // If report id = 0, skip ID field
102 len = tu_min8(len, CFG_TUD_HID_EP_BUFSIZE);
103 memcpy(p_hid->epin_buf, report, len);
104 }
105
106 return usbd_edpt_xfer(TUD_OPT_RHPORT, p_hid->ep_in, p_hid->epin_buf, len);
107}
108
109uint8_t tud_hid_n_interface_protocol(uint8_t instance)
110{
111 return _hidd_itf[instance].itf_protocol;
112}
113
114uint8_t tud_hid_n_get_protocol(uint8_t instance)
115{
116 return _hidd_itf[instance].protocol_mode;
117}
118
119bool tud_hid_n_keyboard_report(uint8_t instance, uint8_t report_id, uint8_t modifier, uint8_t keycode[6])
120{
121 hid_keyboard_report_t report;
122
123 report.modifier = modifier;
124 report.reserved = 0;
125
126 if ( keycode )
127 {
128 memcpy(report.keycode, keycode, 6);
129 }else
130 {
131 tu_memclr(report.keycode, 6);
132 }
133
134 return tud_hid_n_report(instance, report_id, &report, sizeof(report));
135}
136
137bool tud_hid_n_mouse_report(uint8_t instance, uint8_t report_id,
138 uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal)
139{
140 hid_mouse_report_t report =
141 {
142 .buttons = buttons,
143 .x = x,
144 .y = y,
145 .wheel = vertical,
146 .pan = horizontal
147 };
148
149 return tud_hid_n_report(instance, report_id, &report, sizeof(report));
150}
151
152bool tud_hid_n_gamepad_report(uint8_t instance, uint8_t report_id,
153 int8_t x, int8_t y, int8_t z, int8_t rz, int8_t rx, int8_t ry, uint8_t hat, uint32_t buttons)
154{
155 hid_gamepad_report_t report =
156 {
157 .x = x,
158 .y = y,
159 .z = z,
160 .rz = rz,
161 .rx = rx,
162 .ry = ry,
163 .hat = hat,
164 .buttons = buttons,
165 };
166
167 return tud_hid_n_report(instance, report_id, &report, sizeof(report));
168}
169
170//--------------------------------------------------------------------+
171// USBD-CLASS API
172//--------------------------------------------------------------------+
173void hidd_init(void)
174{
175 hidd_reset(TUD_OPT_RHPORT);
176}
177
178void hidd_reset(uint8_t rhport)
179{
180 (void) rhport;
181 tu_memclr(_hidd_itf, sizeof(_hidd_itf));
182}
183
184uint16_t hidd_open(uint8_t rhport, tusb_desc_interface_t const * desc_itf, uint16_t max_len)
185{
186 TU_VERIFY(TUSB_CLASS_HID == desc_itf->bInterfaceClass, 0);
187
188 // len = interface + hid + n*endpoints
189 uint16_t const drv_len = sizeof(tusb_desc_interface_t) + sizeof(tusb_hid_descriptor_hid_t) + desc_itf->bNumEndpoints*sizeof(tusb_desc_endpoint_t);
190 TU_ASSERT(max_len >= drv_len, 0);
191
192 // Find available interface
193 hidd_interface_t * p_hid = NULL;
194 uint8_t hid_id;
195 for(hid_id=0; hid_id<CFG_TUD_HID; hid_id++)
196 {
197 if ( _hidd_itf[hid_id].ep_in == 0 )
198 {
199 p_hid = &_hidd_itf[hid_id];
200 break;
201 }
202 }
203 TU_ASSERT(p_hid, 0);
204
205 uint8_t const *p_desc = (uint8_t const *) desc_itf;
206
207 //------------- HID descriptor -------------//
208 p_desc = tu_desc_next(p_desc);
209 TU_ASSERT(HID_DESC_TYPE_HID == tu_desc_type(p_desc), 0);
210 p_hid->hid_descriptor = (tusb_hid_descriptor_hid_t const *) p_desc;
211
212 //------------- Endpoint Descriptor -------------//
213 p_desc = tu_desc_next(p_desc);
214 TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, desc_itf->bNumEndpoints, TUSB_XFER_INTERRUPT, &p_hid->ep_out, &p_hid->ep_in), 0);
215
216 if ( desc_itf->bInterfaceSubClass == HID_SUBCLASS_BOOT ) p_hid->itf_protocol = desc_itf->bInterfaceProtocol;
217
218 p_hid->protocol_mode = HID_PROTOCOL_REPORT; // Per Specs: default is report mode
219 p_hid->itf_num = desc_itf->bInterfaceNumber;
220
221 // Use offsetof to avoid pointer to the odd/misaligned address
222 p_hid->report_desc_len = tu_unaligned_read16((uint8_t const*) p_hid->hid_descriptor + offsetof(tusb_hid_descriptor_hid_t, wReportLength));
223
224 // Prepare for output endpoint
225 if (p_hid->ep_out)
226 {
227 if ( !usbd_edpt_xfer(rhport, p_hid->ep_out, p_hid->epout_buf, sizeof(p_hid->epout_buf)) )
228 {
229 TU_LOG_FAILED();
230 TU_BREAKPOINT();
231 }
232 }
233
234 return drv_len;
235}
236
237// Invoked when a control transfer occurred on an interface of this class
238// Driver response accordingly to the request and the transfer stage (setup/data/ack)
239// return false to stall control endpoint (e.g unsupported request)
240bool hidd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request)
241{
242 TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE);
243
244 uint8_t const hid_itf = get_index_by_itfnum((uint8_t) request->wIndex);
245 TU_VERIFY(hid_itf < CFG_TUD_HID);
246
247 hidd_interface_t* p_hid = &_hidd_itf[hid_itf];
248
249 if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD)
250 {
251 //------------- STD Request -------------//
252 if ( stage == CONTROL_STAGE_SETUP )
253 {
254 uint8_t const desc_type = tu_u16_high(request->wValue);
255 //uint8_t const desc_index = tu_u16_low (request->wValue);
256
257 if (request->bRequest == TUSB_REQ_GET_DESCRIPTOR && desc_type == HID_DESC_TYPE_HID)
258 {
259 TU_VERIFY(p_hid->hid_descriptor);
260 TU_VERIFY(tud_control_xfer(rhport, request, (void*)(uintptr_t) p_hid->hid_descriptor, p_hid->hid_descriptor->bLength));
261 }
262 else if (request->bRequest == TUSB_REQ_GET_DESCRIPTOR && desc_type == HID_DESC_TYPE_REPORT)
263 {
264 uint8_t const * desc_report = tud_hid_descriptor_report_cb(hid_itf);
265 tud_control_xfer(rhport, request, (void*)(uintptr_t) desc_report, p_hid->report_desc_len);
266 }
267 else
268 {
269 return false; // stall unsupported request
270 }
271 }
272 }
273 else if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS)
274 {
275 //------------- Class Specific Request -------------//
276 switch( request->bRequest )
277 {
278 case HID_REQ_CONTROL_GET_REPORT:
279 if ( stage == CONTROL_STAGE_SETUP )
280 {
281 uint8_t const report_type = tu_u16_high(request->wValue);
282 uint8_t const report_id = tu_u16_low(request->wValue);
283
284 uint8_t* report_buf = p_hid->epin_buf;
285 uint16_t req_len = tu_min16(request->wLength, CFG_TUD_HID_EP_BUFSIZE);
286
287 uint16_t xferlen = 0;
288
289 // If host request a specific Report ID, add ID to as 1 byte of response
290 if ( (report_id != HID_REPORT_TYPE_INVALID) && (req_len > 1) )
291 {
292 *report_buf++ = report_id;
293 req_len--;
294
295 xferlen++;
296 }
297
298 xferlen += tud_hid_get_report_cb(hid_itf, report_id, (hid_report_type_t) report_type, report_buf, req_len);
299 TU_ASSERT( xferlen > 0 );
300
301 tud_control_xfer(rhport, request, p_hid->epin_buf, xferlen);
302 }
303 break;
304
305 case HID_REQ_CONTROL_SET_REPORT:
306 if ( stage == CONTROL_STAGE_SETUP )
307 {
308 TU_VERIFY(request->wLength <= sizeof(p_hid->epout_buf));
309 tud_control_xfer(rhport, request, p_hid->epout_buf, request->wLength);
310 }
311 else if ( stage == CONTROL_STAGE_ACK )
312 {
313 uint8_t const report_type = tu_u16_high(request->wValue);
314 uint8_t const report_id = tu_u16_low(request->wValue);
315
316 uint8_t const* report_buf = p_hid->epout_buf;
317 uint16_t report_len = tu_min16(request->wLength, CFG_TUD_HID_EP_BUFSIZE);
318
319 // If host request a specific Report ID, extract report ID in buffer before invoking callback
320 if ( (report_id != HID_REPORT_TYPE_INVALID) && (report_len > 1) && (report_id == report_buf[0]) )
321 {
322 report_buf++;
323 report_len--;
324 }
325
326 tud_hid_set_report_cb(hid_itf, report_id, (hid_report_type_t) report_type, report_buf, report_len);
327 }
328 break;
329
330 case HID_REQ_CONTROL_SET_IDLE:
331 if ( stage == CONTROL_STAGE_SETUP )
332 {
333 p_hid->idle_rate = tu_u16_high(request->wValue);
334 if ( tud_hid_set_idle_cb )
335 {
336 // stall request if callback return false
337 TU_VERIFY( tud_hid_set_idle_cb( hid_itf, p_hid->idle_rate) );
338 }
339
340 tud_control_status(rhport, request);
341 }
342 break;
343
344 case HID_REQ_CONTROL_GET_IDLE:
345 if ( stage == CONTROL_STAGE_SETUP )
346 {
347 // TODO idle rate of report
348 tud_control_xfer(rhport, request, &p_hid->idle_rate, 1);
349 }
350 break;
351
352 case HID_REQ_CONTROL_GET_PROTOCOL:
353 if ( stage == CONTROL_STAGE_SETUP )
354 {
355 tud_control_xfer(rhport, request, &p_hid->protocol_mode, 1);
356 }
357 break;
358
359 case HID_REQ_CONTROL_SET_PROTOCOL:
360 if ( stage == CONTROL_STAGE_SETUP )
361 {
362 tud_control_status(rhport, request);
363 }
364 else if ( stage == CONTROL_STAGE_ACK )
365 {
366 p_hid->protocol_mode = (uint8_t) request->wValue;
367 if (tud_hid_set_protocol_cb)
368 {
369 tud_hid_set_protocol_cb(hid_itf, p_hid->protocol_mode);
370 }
371 }
372 break;
373
374 default: return false; // stall unsupported request
375 }
376 }else
377 {
378 return false; // stall unsupported request
379 }
380
381 return true;
382}
383
384bool hidd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes)
385{
386 (void) result;
387
388 uint8_t instance = 0;
389 hidd_interface_t * p_hid = _hidd_itf;
390
391 // Identify which interface to use
392 for (instance = 0; instance < CFG_TUD_HID; instance++)
393 {
394 p_hid = &_hidd_itf[instance];
395 if ( (ep_addr == p_hid->ep_out) || (ep_addr == p_hid->ep_in) ) break;
396 }
397 TU_ASSERT(instance < CFG_TUD_HID);
398
399 // Sent report successfully
400 if (ep_addr == p_hid->ep_in)
401 {
402 if (tud_hid_report_complete_cb)
403 {
404 tud_hid_report_complete_cb(instance, p_hid->epin_buf, (uint8_t) xferred_bytes);
405 }
406 }
407 // Received report
408 else if (ep_addr == p_hid->ep_out)
409 {
410 tud_hid_set_report_cb(instance, 0, HID_REPORT_TYPE_INVALID, p_hid->epout_buf, xferred_bytes);
411 TU_ASSERT(usbd_edpt_xfer(rhport, p_hid->ep_out, p_hid->epout_buf, sizeof(p_hid->epout_buf)));
412 }
413
414 return true;
415}
416
417#endif