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
*
* Copyright (c) 2019-2022 CTCaer
* Copyright (c) 2019-2025 CTCaer
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
@@ -80,6 +80,8 @@ enum {
static jc_cal_t jc_cal_ctx;
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)
{
// 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)
{
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)
{
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)
{
int res = _jc_poll((gamepad_report_t *)USB_EP_BULK_IN_BUF_ADDR);
int res = _jc_poll(rpt_buffer);
if (res == INPUT_POLL_EXIT)
return true;
// 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)))
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)
{
_fts_touch_read((touchpad_report_t *)USB_EP_BULK_IN_BUF_ADDR);
_fts_touch_read(rpt_buffer);
// Send HID report.
if (_hid_transfer_start(usbs, sizeof(touchpad_report_t)))
@@ -399,6 +401,10 @@ int usb_device_gadget_hid(usb_ctxt_t *usbs)
else
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)
{
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");
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;
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();
// 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.
if (usbs->type == USB_HID_GAMEPAD)
{
@@ -448,13 +462,6 @@ int usb_device_gadget_hid(usb_ctxt_t *usbs)
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.
timer = get_tmr_us() - timer;
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)
{
if (usb_ops.usbd_handle_ep0_ctrl_setup())
if (usb_ops.usbd_handle_ep0_ctrl_setup(NULL))
raise_exception(ums, UMS_STATE_PROTOCOL_RESET);
}

View File

@@ -101,7 +101,11 @@ typedef struct _usbd_controller_t
bool configuration_set;
bool max_lun_set;
bool bulk_reset_req;
u32 intr_idle_rate;
bool intr_idle_req;
bool hid_report_sent;
void *hid_rpt_buffer;
u32 hid_rpt_size;
u32 charger_detect;
} 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;
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;
return;
@@ -899,20 +903,48 @@ static void _usbd_handle_get_class_request(bool *transmit_data, u8 *desc, int *s
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:
if (_wLength)
break;
_usbd_ep_ack(USB_EP_CTRL_IN);
usbd_otg->bulk_reset_req = true;
break; // DELAYED_STATUS;
return; // DELAYED_STATUS;
case USB_REQUEST_BULK_GET_MAX_LUN:
if (_wLength != 1)
break;
*transmit_data = true;
*size = 1;
desc[0] = usbd_otg->max_lun; // Set 0 LUN for 1 drive supported.
usbd_otg->max_lun_set = true;
break;
return;
default:
*ep_stall = true;
break;
}
*ep_stall = true;
}
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();
}
int usbd_handle_ep0_ctrl_setup()
int usbd_handle_ep0_ctrl_setup(u32 *data)
{
// Acknowledge setup request for EP0 and copy its configuration.
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);
}
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.
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))
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));
@@ -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))
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));
@@ -1574,7 +1615,7 @@ int usb_device_class_send_max_lun(u8 max_lun)
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))
return USB_ERROR_USER_ABORT;
}
@@ -1582,15 +1623,19 @@ int usb_device_class_send_max_lun(u8 max_lun)
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.
u32 timer = get_tmr_ms() + 10000;
// Wait for request and transfer start.
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))
return USB_ERROR_USER_ABORT;
}

View File

@@ -1,7 +1,7 @@
/*
* 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
* 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_XFER_START 0
#define USB_XFER_SYNCED_ENUM 1000000
#define USB_XFER_SYNCED_CMD 1000000
#define USB_XFER_SYNCED_DATA 2000000
#define USB_XFER_SYNCED_CLASS 5000000
#define USB_XFER_SYNCED -1
#define USB_XFER_SYNCED_ENUM 1000000 // ~2s.
#define USB_XFER_SYNCED_CMD 1000000 // ~2s.
#define USB_XFER_SYNCED_DATA 2000000 // ~4s.
#define USB_XFER_SYNCED_CLASS 5000000 // ~10s.
#define USB_XFER_SYNCED -1 // Max.
typedef enum _usb_hid_type
{
@@ -122,6 +122,9 @@ typedef enum {
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_RESET = 0xFF
} usb_standard_req_t;
@@ -167,12 +170,13 @@ typedef struct _usb_ops_t
{
int (*usbd_flush_endpoint)(u32);
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);
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_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_big)(u8 *, u32, u32 *);
@@ -186,10 +190,17 @@ typedef struct _usb_ops_t
typedef struct _usb_ctxt_t
{
u32 type;
// UMS.
u32 partition;
u32 offset;
u32 sectors;
u32 ro;
// HID.
u32 idle;
// System.
void (*system_maintenance)(bool);
void *label;
void (*set_text)(void *, const char *);

View File

@@ -381,6 +381,10 @@ typedef struct _xusbd_controller_t
u8 max_lun;
bool max_lun_set;
void *hid_rpt_buffer;
u32 hid_rpt_size;
u32 intr_idle_rate;
bool intr_idle_req;
bool bulk_reset_req;
} 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;
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;
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:
if (_wLength)
goto stall;
usbd_xotg->bulk_reset_req = true;
return _xusb_issue_status_trb(USB_DIR_IN); // DELAYED_STATUS;
case USB_REQUEST_BULK_GET_MAX_LUN:
if (!usbd_xotg->max_lun_set)
if (_wLength != 1 || !usbd_xotg->max_lun_set)
goto stall;
usbd_xotg->device_state = XUSB_LUN_CONFIGURED_STS_WAIT;
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();
}
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.
* 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)
{
usbd_xotg->bulk_reset_req = false;
@@ -2178,8 +2213,12 @@ bool xusb_device_class_send_max_lun(u8 max_lun)
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.
u32 timer = get_tmr_ms() + 10000;