bdk: usb: revamp hid logic

- Add support for GET REPORT. Allows OS to get a single input report.
- Add support for SET_IDLE. Allows OS to control when to send input reports

The SET IDLE and the underlying logic change fixes several things:
- The old issue of congestion in some systems.
- The new bug that would not allow setup packets to be received because mode
 was set to only send when there are changes.
- Now this starts properly as the old code but allows to be changed by OS on
 demand, while continuing servicing setup packets.
This commit is contained in:
CTCaer
2025-06-22 13:24:47 +03:00
parent e1ea05d53a
commit 40b05ea5ea
5 changed files with 143 additions and 41 deletions

View File

@@ -1,7 +1,7 @@
/* /*
* USB Gadget HID driver for Tegra X1 * USB Gadget HID driver for Tegra X1
* *
* Copyright (c) 2019-2022 CTCaer * Copyright (c) 2019-2025 CTCaer
* *
* This program is free software; you can redistribute it and/or modify it * This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License, * under the terms and conditions of the GNU General Public License,
@@ -80,6 +80,8 @@ enum {
static jc_cal_t jc_cal_ctx; static jc_cal_t jc_cal_ctx;
static usb_ops_t usb_ops; static usb_ops_t usb_ops;
static void *rpt_buffer = (u8 *)USB_EP_BULK_IN_BUF_ADDR;
static bool _jc_calibration(const jc_gamepad_rpt_t *jc_pad) static bool _jc_calibration(const jc_gamepad_rpt_t *jc_pad)
{ {
// Calibrate left stick. // Calibrate left stick.
@@ -347,7 +349,7 @@ static bool _fts_touch_read(touchpad_report_t *rpt)
static u8 _hid_transfer_start(usb_ctxt_t *usbs, u32 len) static u8 _hid_transfer_start(usb_ctxt_t *usbs, u32 len)
{ {
u8 status = usb_ops.usb_device_ep1_in_write((u8 *)USB_EP_BULK_IN_BUF_ADDR, len, NULL, USB_XFER_SYNCED_CMD); u8 status = usb_ops.usb_device_ep1_in_write(rpt_buffer, len, NULL, USB_XFER_SYNCED_CMD);
if (status == USB_ERROR_XFER_ERROR) if (status == USB_ERROR_XFER_ERROR)
{ {
usbs->set_text(usbs->label, "#FFDD00 Error:# EP IN transfer!"); usbs->set_text(usbs->label, "#FFDD00 Error:# EP IN transfer!");
@@ -364,12 +366,12 @@ static u8 _hid_transfer_start(usb_ctxt_t *usbs, u32 len)
static bool _hid_poll_jc(usb_ctxt_t *usbs) static bool _hid_poll_jc(usb_ctxt_t *usbs)
{ {
int res = _jc_poll((gamepad_report_t *)USB_EP_BULK_IN_BUF_ADDR); int res = _jc_poll(rpt_buffer);
if (res == INPUT_POLL_EXIT) if (res == INPUT_POLL_EXIT)
return true; return true;
// Send HID report. // Send HID report.
if (res == INPUT_POLL_HAS_PACKET) if (res == INPUT_POLL_HAS_PACKET || usbs->idle)
if (_hid_transfer_start(usbs, sizeof(gamepad_report_t))) if (_hid_transfer_start(usbs, sizeof(gamepad_report_t)))
return true; // EP Error. return true; // EP Error.
@@ -378,7 +380,7 @@ static bool _hid_poll_jc(usb_ctxt_t *usbs)
static bool _hid_poll_touch(usb_ctxt_t *usbs) static bool _hid_poll_touch(usb_ctxt_t *usbs)
{ {
_fts_touch_read((touchpad_report_t *)USB_EP_BULK_IN_BUF_ADDR); _fts_touch_read(rpt_buffer);
// Send HID report. // Send HID report.
if (_hid_transfer_start(usbs, sizeof(touchpad_report_t))) if (_hid_transfer_start(usbs, sizeof(touchpad_report_t)))
@@ -399,6 +401,10 @@ int usb_device_gadget_hid(usb_ctxt_t *usbs)
else else
xusb_device_get_ops(&usb_ops); xusb_device_get_ops(&usb_ops);
// Always push packets by default.
//! TODO: For now only per polling rate or on change is supported.
usbs->idle = 1;
if (usbs->type == USB_HID_GAMEPAD) if (usbs->type == USB_HID_GAMEPAD)
{ {
polling_time = 15000; polling_time = 15000;
@@ -426,7 +432,8 @@ int usb_device_gadget_hid(usb_ctxt_t *usbs)
usbs->set_text(usbs->label, "#C7EA46 Status:# Waiting for HID report request"); usbs->set_text(usbs->label, "#C7EA46 Status:# Waiting for HID report request");
if (usb_ops.usb_device_class_send_hid_report()) u32 rpt_size = usbs->type == USB_HID_GAMEPAD ? sizeof(gamepad_report_t) : sizeof(touchpad_report_t);
if (usb_ops.usb_device_class_send_hid_report(rpt_buffer, rpt_size))
goto error; goto error;
usbs->set_text(usbs->label, "#C7EA46 Status:# Started HID emulation"); usbs->set_text(usbs->label, "#C7EA46 Status:# Started HID emulation");
@@ -436,6 +443,13 @@ int usb_device_gadget_hid(usb_ctxt_t *usbs)
{ {
u32 timer = get_tmr_us(); u32 timer = get_tmr_us();
// Check for suspended USB in case the cable was pulled.
if (usb_ops.usb_device_get_suspended())
break; // Disconnected.
// Handle control endpoint.
usb_ops.usbd_handle_ep0_ctrl_setup(&usbs->idle);
// Parse input device. // Parse input device.
if (usbs->type == USB_HID_GAMEPAD) if (usbs->type == USB_HID_GAMEPAD)
{ {
@@ -448,13 +462,6 @@ int usb_device_gadget_hid(usb_ctxt_t *usbs)
break; break;
} }
// Check for suspended USB in case the cable was pulled.
if (usb_ops.usb_device_get_suspended())
break; // Disconnected.
// Handle control endpoint.
usb_ops.usbd_handle_ep0_ctrl_setup();
// Wait max gadget timing. // Wait max gadget timing.
timer = get_tmr_us() - timer; timer = get_tmr_us() - timer;
if (timer < polling_time) if (timer < polling_time)

View File

@@ -285,7 +285,7 @@ static void raise_exception(usbd_gadget_ums_t *ums, enum ums_state new_state)
static void _handle_ep0_ctrl(usbd_gadget_ums_t *ums) static void _handle_ep0_ctrl(usbd_gadget_ums_t *ums)
{ {
if (usb_ops.usbd_handle_ep0_ctrl_setup()) if (usb_ops.usbd_handle_ep0_ctrl_setup(NULL))
raise_exception(ums, UMS_STATE_PROTOCOL_RESET); raise_exception(ums, UMS_STATE_PROTOCOL_RESET);
} }

View File

@@ -101,7 +101,11 @@ typedef struct _usbd_controller_t
bool configuration_set; bool configuration_set;
bool max_lun_set; bool max_lun_set;
bool bulk_reset_req; bool bulk_reset_req;
u32 intr_idle_rate;
bool intr_idle_req;
bool hid_report_sent; bool hid_report_sent;
void *hid_rpt_buffer;
u32 hid_rpt_size;
u32 charger_detect; u32 charger_detect;
} usbd_controller_t; } usbd_controller_t;
@@ -889,9 +893,9 @@ static void _usbd_handle_get_class_request(bool *transmit_data, u8 *desc, int *s
u16 _wLength = usbd_otg->control_setup.wLength; u16 _wLength = usbd_otg->control_setup.wLength;
bool valid_interface = _wIndex == usbd_otg->interface_num; bool valid_interface = _wIndex == usbd_otg->interface_num;
bool valid_len = (_bRequest == USB_REQUEST_BULK_GET_MAX_LUN) ? 1 : 0; bool valid_val = (_bRequest >= USB_REQUEST_BULK_GET_MAX_LUN) ? (!_wValue) : true;
if (!valid_interface || _wValue != 0 || _wLength != valid_len) if (!valid_interface || !valid_val)
{ {
*ep_stall = true; *ep_stall = true;
return; return;
@@ -899,20 +903,48 @@ static void _usbd_handle_get_class_request(bool *transmit_data, u8 *desc, int *s
switch (_bRequest) switch (_bRequest)
{ {
case USB_REQUEST_INTR_GET_REPORT:
if (usbd_otg->hid_rpt_size != _wLength)
break;
// _wValue unused as there's only one report type and id.
*transmit_data = true;
*size = usbd_otg->hid_rpt_size;
memcpy(desc, usbd_otg->hid_rpt_buffer, usbd_otg->hid_rpt_size);
return;
case USB_REQUEST_INTR_SET_IDLE:
if (_wLength)
break;
usbd_otg->intr_idle_rate = (_wValue & 0xFF) * 4 * 1000; // Only one interface so upper byte ignored.
usbd_otg->intr_idle_req = true;
_usbd_ep_ack(USB_EP_CTRL_IN);
return; // DELAYED_STATUS;
case USB_REQUEST_BULK_RESET: case USB_REQUEST_BULK_RESET:
if (_wLength)
break;
_usbd_ep_ack(USB_EP_CTRL_IN); _usbd_ep_ack(USB_EP_CTRL_IN);
usbd_otg->bulk_reset_req = true; usbd_otg->bulk_reset_req = true;
break; // DELAYED_STATUS; return; // DELAYED_STATUS;
case USB_REQUEST_BULK_GET_MAX_LUN: case USB_REQUEST_BULK_GET_MAX_LUN:
if (_wLength != 1)
break;
*transmit_data = true; *transmit_data = true;
*size = 1; *size = 1;
desc[0] = usbd_otg->max_lun; // Set 0 LUN for 1 drive supported. desc[0] = usbd_otg->max_lun; // Set 0 LUN for 1 drive supported.
usbd_otg->max_lun_set = true; usbd_otg->max_lun_set = true;
break; return;
default: default:
*ep_stall = true;
break; break;
} }
*ep_stall = true;
} }
static void _usbd_handle_get_descriptor(bool *transmit_data, void **desc, int *size, bool *ep_stall) static void _usbd_handle_get_descriptor(bool *transmit_data, void **desc, int *size, bool *ep_stall)
@@ -1395,7 +1427,7 @@ int usb_device_enumerate(usb_gadget_type gadget)
return _usbd_ep0_initialize(); return _usbd_ep0_initialize();
} }
int usbd_handle_ep0_ctrl_setup() int usbd_handle_ep0_ctrl_setup(u32 *data)
{ {
// Acknowledge setup request for EP0 and copy its configuration. // Acknowledge setup request for EP0 and copy its configuration.
u32 ep0_setup_req = usbd_otg->regs->endptsetupstat; u32 ep0_setup_req = usbd_otg->regs->endptsetupstat;
@@ -1407,6 +1439,15 @@ int usbd_handle_ep0_ctrl_setup()
memset(usb_ep0_ctrl_buf, 0, USB_TD_BUFFER_PAGE_SIZE); memset(usb_ep0_ctrl_buf, 0, USB_TD_BUFFER_PAGE_SIZE);
} }
if (usbd_otg->intr_idle_req)
{
if (data)
*data = usbd_otg->intr_idle_rate;
usbd_otg->intr_idle_req = false;
return USB_RES_OK;
}
// Only return error if bulk reset was requested. // Only return error if bulk reset was requested.
if (usbd_otg->bulk_reset_req) if (usbd_otg->bulk_reset_req)
{ {
@@ -1489,7 +1530,7 @@ int usb_device_ep1_out_reading_finish(u32 *pending_bytes, u32 sync_timeout)
if ((ep_status == USB_EP_STATUS_IDLE) || (ep_status == USB_EP_STATUS_DISABLED)) if ((ep_status == USB_EP_STATUS_IDLE) || (ep_status == USB_EP_STATUS_DISABLED))
break; break;
usbd_handle_ep0_ctrl_setup(); usbd_handle_ep0_ctrl_setup(NULL);
} }
while ((ep_status == USB_EP_STATUS_ACTIVE) || (ep_status == USB_EP_STATUS_STALLED)); while ((ep_status == USB_EP_STATUS_ACTIVE) || (ep_status == USB_EP_STATUS_STALLED));
@@ -1538,7 +1579,7 @@ int usb_device_ep1_in_writing_finish(u32 *pending_bytes, u32 sync_timeout)
if ((ep_status == USB_EP_STATUS_IDLE) || (ep_status == USB_EP_STATUS_DISABLED)) if ((ep_status == USB_EP_STATUS_IDLE) || (ep_status == USB_EP_STATUS_DISABLED))
break; break;
usbd_handle_ep0_ctrl_setup(); usbd_handle_ep0_ctrl_setup(NULL);
} }
while ((ep_status == USB_EP_STATUS_ACTIVE) || (ep_status == USB_EP_STATUS_STALLED)); while ((ep_status == USB_EP_STATUS_ACTIVE) || (ep_status == USB_EP_STATUS_STALLED));
@@ -1574,7 +1615,7 @@ int usb_device_class_send_max_lun(u8 max_lun)
while (!usbd_otg->max_lun_set) while (!usbd_otg->max_lun_set)
{ {
usbd_handle_ep0_ctrl_setup(); usbd_handle_ep0_ctrl_setup(NULL);
if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN)) if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN))
return USB_ERROR_USER_ABORT; return USB_ERROR_USER_ABORT;
} }
@@ -1582,15 +1623,19 @@ int usb_device_class_send_max_lun(u8 max_lun)
return USB_RES_OK; return USB_RES_OK;
} }
int usb_device_class_send_hid_report() int usb_device_class_send_hid_report(void *rpt_buffer, u32 rpt_size)
{ {
// Set buffers.
usbd_otg->hid_rpt_buffer = rpt_buffer;
usbd_otg->hid_rpt_size = rpt_size;
// Timeout if get GET_HID_REPORT request doesn't happen in 10s. // Timeout if get GET_HID_REPORT request doesn't happen in 10s.
u32 timer = get_tmr_ms() + 10000; u32 timer = get_tmr_ms() + 10000;
// Wait for request and transfer start. // Wait for request and transfer start.
while (!usbd_otg->hid_report_sent) while (!usbd_otg->hid_report_sent)
{ {
usbd_handle_ep0_ctrl_setup(); usbd_handle_ep0_ctrl_setup(NULL);
if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN)) if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN))
return USB_ERROR_USER_ABORT; return USB_ERROR_USER_ABORT;
} }

View File

@@ -1,7 +1,7 @@
/* /*
* Enhanced & eXtensible USB Device (EDCI & XDCI) driver for Tegra X1 * Enhanced & eXtensible USB Device (EDCI & XDCI) driver for Tegra X1
* *
* Copyright (c) 2019-2021 CTCaer * Copyright (c) 2019-2025 CTCaer
* *
* This program is free software; you can redistribute it and/or modify it * This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License, * under the terms and conditions of the GNU General Public License,
@@ -31,11 +31,11 @@
#define USB_EP_BUFFER_ALIGN (USB_TD_BUFFER_PAGE_SIZE) #define USB_EP_BUFFER_ALIGN (USB_TD_BUFFER_PAGE_SIZE)
#define USB_XFER_START 0 #define USB_XFER_START 0
#define USB_XFER_SYNCED_ENUM 1000000 #define USB_XFER_SYNCED_ENUM 1000000 // ~2s.
#define USB_XFER_SYNCED_CMD 1000000 #define USB_XFER_SYNCED_CMD 1000000 // ~2s.
#define USB_XFER_SYNCED_DATA 2000000 #define USB_XFER_SYNCED_DATA 2000000 // ~4s.
#define USB_XFER_SYNCED_CLASS 5000000 #define USB_XFER_SYNCED_CLASS 5000000 // ~10s.
#define USB_XFER_SYNCED -1 #define USB_XFER_SYNCED -1 // Max.
typedef enum _usb_hid_type typedef enum _usb_hid_type
{ {
@@ -122,6 +122,9 @@ typedef enum {
USB_REQUEST_GET_MS_DESCRIPTOR = 0x99, USB_REQUEST_GET_MS_DESCRIPTOR = 0x99,
USB_REQUEST_INTR_GET_REPORT = 1,
USB_REQUEST_INTR_SET_IDLE = 10,
USB_REQUEST_BULK_GET_MAX_LUN = 0xFE, USB_REQUEST_BULK_GET_MAX_LUN = 0xFE,
USB_REQUEST_BULK_RESET = 0xFF USB_REQUEST_BULK_RESET = 0xFF
} usb_standard_req_t; } usb_standard_req_t;
@@ -167,12 +170,13 @@ typedef struct _usb_ops_t
{ {
int (*usbd_flush_endpoint)(u32); int (*usbd_flush_endpoint)(u32);
int (*usbd_set_ep_stall)(u32, int); int (*usbd_set_ep_stall)(u32, int);
int (*usbd_handle_ep0_ctrl_setup)(); int (*usbd_handle_ep0_ctrl_setup)(u32 *);
void (*usbd_end)(bool, bool); void (*usbd_end)(bool, bool);
int (*usb_device_init)(); int (*usb_device_init)();
int (*usb_device_enumerate)(usb_gadget_type gadget); int (*usb_device_enumerate)(usb_gadget_type);
int (*usb_device_class_send_max_lun)(u8); int (*usb_device_class_send_max_lun)(u8);
int (*usb_device_class_send_hid_report)(); int (*usb_device_class_send_hid_report)(void *, u32);
int (*usb_device_ep1_out_read)(u8 *, u32, u32 *, u32); int (*usb_device_ep1_out_read)(u8 *, u32, u32 *, u32);
int (*usb_device_ep1_out_read_big)(u8 *, u32, u32 *); int (*usb_device_ep1_out_read_big)(u8 *, u32, u32 *);
@@ -186,10 +190,17 @@ typedef struct _usb_ops_t
typedef struct _usb_ctxt_t typedef struct _usb_ctxt_t
{ {
u32 type; u32 type;
// UMS.
u32 partition; u32 partition;
u32 offset; u32 offset;
u32 sectors; u32 sectors;
u32 ro; u32 ro;
// HID.
u32 idle;
// System.
void (*system_maintenance)(bool); void (*system_maintenance)(bool);
void *label; void *label;
void (*set_text)(void *, const char *); void (*set_text)(void *, const char *);
@@ -201,4 +212,4 @@ void xusb_device_get_ops(usb_ops_t *ops);
int usb_device_gadget_ums(usb_ctxt_t *usbs); int usb_device_gadget_ums(usb_ctxt_t *usbs);
int usb_device_gadget_hid(usb_ctxt_t *usbs); int usb_device_gadget_hid(usb_ctxt_t *usbs);
#endif #endif

View File

@@ -381,6 +381,10 @@ typedef struct _xusbd_controller_t
u8 max_lun; u8 max_lun;
bool max_lun_set; bool max_lun_set;
void *hid_rpt_buffer;
u32 hid_rpt_size;
u32 intr_idle_rate;
bool intr_idle_req;
bool bulk_reset_req; bool bulk_reset_req;
} xusbd_controller_t; } xusbd_controller_t;
@@ -1469,20 +1473,39 @@ static int _xusb_handle_get_class_request(const usb_ctrl_setup_t *ctrl_setup)
u16 _wLength = ctrl_setup->wLength; u16 _wLength = ctrl_setup->wLength;
bool valid_interface = _wIndex == usbd_xotg->interface_num; bool valid_interface = _wIndex == usbd_xotg->interface_num;
bool valid_len = (_bRequest == USB_REQUEST_BULK_GET_MAX_LUN) ? 1 : 0; bool valid_val = (_bRequest >= USB_REQUEST_BULK_GET_MAX_LUN) ? (!_wValue) : true;
if (!valid_interface || _wValue != 0 || _wLength != valid_len) if (!valid_interface || !valid_val)
goto stall; goto stall;
switch (_bRequest) switch (_bRequest)
{ {
case USB_REQUEST_INTR_GET_REPORT:
if (usbd_xotg->hid_rpt_size != _wLength)
goto stall;
// _wValue unused as there's only one report type and id.
return _xusb_issue_data_trb(usbd_xotg->hid_rpt_buffer, usbd_xotg->hid_rpt_size, USB_DIR_IN);
case USB_REQUEST_INTR_SET_IDLE:
if (_wLength)
goto stall;
usbd_xotg->intr_idle_rate = (_wValue & 0xFF) * 4 * 1000; // Only one interface so upper byte ignored.
usbd_xotg->intr_idle_req = true;
return _xusb_issue_status_trb(USB_DIR_IN); // DELAYED_STATUS;
case USB_REQUEST_BULK_RESET: case USB_REQUEST_BULK_RESET:
if (_wLength)
goto stall;
usbd_xotg->bulk_reset_req = true; usbd_xotg->bulk_reset_req = true;
return _xusb_issue_status_trb(USB_DIR_IN); // DELAYED_STATUS; return _xusb_issue_status_trb(USB_DIR_IN); // DELAYED_STATUS;
case USB_REQUEST_BULK_GET_MAX_LUN: case USB_REQUEST_BULK_GET_MAX_LUN:
if (!usbd_xotg->max_lun_set) if (_wLength != 1 || !usbd_xotg->max_lun_set)
goto stall; goto stall;
usbd_xotg->device_state = XUSB_LUN_CONFIGURED_STS_WAIT; usbd_xotg->device_state = XUSB_LUN_CONFIGURED_STS_WAIT;
return _xusb_issue_data_trb(&usbd_xotg->max_lun, 1, USB_DIR_IN); return _xusb_issue_data_trb(&usbd_xotg->max_lun, 1, USB_DIR_IN);
} }
@@ -2019,12 +2042,24 @@ void xusb_end(bool reset_ep, bool only_controller)
_xusb_device_power_down(); _xusb_device_power_down();
} }
int xusb_handle_ep0_ctrl_setup() int xusb_handle_ep0_ctrl_setup(u32 *data)
{ {
/* /*
* EP0 Control handling is done by normal ep operation in XUSB. * EP0 Control handling is done by normal ep operation in XUSB.
* Here we handle the bulk reset only. * Here we handle the interface only, except if HID.
*/ */
if (usbd_xotg->gadget >= USB_GADGET_HID_GAMEPAD)
_xusb_ep_operation(1);
if (usbd_xotg->intr_idle_req)
{
if (data)
*data = usbd_xotg->intr_idle_rate;
usbd_xotg->intr_idle_req = false;
return USB_RES_OK;
}
if (usbd_xotg->bulk_reset_req) if (usbd_xotg->bulk_reset_req)
{ {
usbd_xotg->bulk_reset_req = false; usbd_xotg->bulk_reset_req = false;
@@ -2178,8 +2213,12 @@ bool xusb_device_class_send_max_lun(u8 max_lun)
return false; return false;
} }
bool xusb_device_class_send_hid_report() bool xusb_device_class_send_hid_report(void *rpt_buffer, u32 rpt_size)
{ {
// Set buffers.
usbd_xotg->hid_rpt_buffer = rpt_buffer;
usbd_xotg->hid_rpt_size = rpt_size;
// Timeout if get GET_HID_REPORT request doesn't happen in 10s. // Timeout if get GET_HID_REPORT request doesn't happen in 10s.
u32 timer = get_tmr_ms() + 10000; u32 timer = get_tmr_ms() + 10000;