diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 5bf72e1fab1a..bdd964e87c48 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -639,6 +639,35 @@ struct dwc3_event_buffer { struct dwc3 *dwc; }; +struct dwc3_gadget_events { + unsigned int disconnect; + unsigned int reset; + unsigned int connect; + unsigned int wakeup; + unsigned int link_status_change; + unsigned int eopf; + unsigned int suspend; + unsigned int sof; + unsigned int erratic_error; + unsigned int overflow; + unsigned int vendor_dev_test_lmp; + unsigned int cmdcmplt; + unsigned int unknown_event; +}; + +struct dwc3_ep_events { + unsigned int xfercomplete; + unsigned int xfernotready; + unsigned int control_data; + unsigned int control_status; + unsigned int xferinprogress; + unsigned int rxtxfifoevent; + unsigned int streamevent; + unsigned int epcmdcomplete; + unsigned int unknown_event; + unsigned int total; +}; + #define DWC3_EP_FLAG_STALLED BIT(0) #define DWC3_EP_FLAG_WEDGED BIT(1) @@ -673,6 +702,9 @@ struct dwc3_event_buffer { * isochronous START TRANSFER command failure workaround * @start_cmd_status: the status of testing START TRANSFER command with * combo_num = 'b00 + * @dbg_ep_events: different events counter for endpoint + * @dbg_ep_events_diff: differential events counter for endpoint + * @dbg_ep_events_ts: timestamp for previous event counters */ struct dwc3_ep { struct usb_ep endpoint; @@ -723,6 +755,10 @@ struct dwc3_ep { /* For isochronous START TRANSFER workaround only */ u8 combo_num; int start_cmd_status; + + struct dwc3_ep_events dbg_ep_events; + struct dwc3_ep_events dbg_ep_events_diff; + ktime_t dbg_ep_events_kt; }; enum dwc3_phy { @@ -1231,6 +1267,7 @@ struct dwc3 { unsigned int index; void *dwc_ipc_log_ctxt; + struct dwc3_gadget_events dbg_gadget_events; }; #define INCRX_BURST_MODE 0 diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index e77015e25af0..986d2f963508 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -56,6 +56,9 @@ } +#define ep_event_rate(ev, c, p, dt) \ + ((dt) ? ((c.ev - p.ev) * (MSEC_PER_SEC)) / (dt) : 0) + static const struct debugfs_reg32 dwc3_regs[] = { dump_register(GSBUSCFG0), dump_register(GSBUSCFG1), @@ -902,9 +905,156 @@ static void dwc3_debugfs_create_endpoint_dirs(struct dwc3 *dwc, } } +static ssize_t dwc3_gadget_int_events_store(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + int i, ret; + unsigned long flags; + struct seq_file *s = file->private_data; + struct dwc3 *dwc = s->private; + struct dwc3_ep *dep; + ktime_t kt; + u8 clear_stats; + + if (ubuf == NULL) { + pr_err("[%s] EINVAL\n", __func__); + ret = -EINVAL; + return ret; + } + + ret = kstrtou8_from_user(ubuf, count, 0, &clear_stats); + if (ret < 0) { + pr_err("can't get enter value.\n"); + return ret; + } + + if (clear_stats != 0) { + pr_err("Wrong value. To clear stats, enter value as 0.\n"); + ret = -EINVAL; + return ret; + } + + pr_debug("%s(): clearing debug interrupt buffers\n", __func__); + spin_lock_irqsave(&dwc->lock, flags); + kt = ktime_get_real(); + for (i = 0; i < DWC3_ENDPOINTS_NUM; i++) { + dep = dwc->eps[i]; + memset(&dep->dbg_ep_events, 0, sizeof(dep->dbg_ep_events)); + memset(&dep->dbg_ep_events_diff, 0, sizeof(dep->dbg_ep_events)); + dep->dbg_ep_events_kt = kt; + } + memset(&dwc->dbg_gadget_events, 0, sizeof(dwc->dbg_gadget_events)); + spin_unlock_irqrestore(&dwc->lock, flags); + return count; +} + +static int dwc3_gadget_int_events_show(struct seq_file *s, void *unused) +{ + unsigned long flags; + struct dwc3 *dwc = s->private; + struct dwc3_gadget_events *dbg_gadget_events; + struct dwc3_ep *dep; + int i; + ktime_t now; + s64 delta_ms; + + spin_lock_irqsave(&dwc->lock, flags); + dbg_gadget_events = &dwc->dbg_gadget_events; + + for (i = 0; i < DWC3_ENDPOINTS_NUM; i++) { + dep = dwc->eps[i]; + + if (dep == NULL || !(dep->flags & DWC3_EP_ENABLED)) + continue; + + now = ktime_get_real(); + delta_ms = ktime_ms_delta(now, dep->dbg_ep_events_kt); + + seq_printf(s, "\n\n===== dbg_ep_events for EP(%d) %s =====\n", + i, dep->name); + seq_printf(s, "xfercomplete:%u @ %llHz\n", + dep->dbg_ep_events.xfercomplete, + ep_event_rate(xfercomplete, dep->dbg_ep_events, + dep->dbg_ep_events_diff, delta_ms)); + seq_printf(s, "xfernotready:%u @ %llHz\n", + dep->dbg_ep_events.xfernotready, + ep_event_rate(xfernotready, dep->dbg_ep_events, + dep->dbg_ep_events_diff, delta_ms)); + seq_printf(s, "control_data:%u @ %llHz\n", + dep->dbg_ep_events.control_data, + ep_event_rate(control_data, dep->dbg_ep_events, + dep->dbg_ep_events_diff, delta_ms)); + seq_printf(s, "control_status:%u @ %llHz\n", + dep->dbg_ep_events.control_status, + ep_event_rate(control_status, dep->dbg_ep_events, + dep->dbg_ep_events_diff, delta_ms)); + seq_printf(s, "xferinprogress:%u @ %llHz\n", + dep->dbg_ep_events.xferinprogress, + ep_event_rate(xferinprogress, dep->dbg_ep_events, + dep->dbg_ep_events_diff, delta_ms)); + seq_printf(s, "rxtxfifoevent:%u @ %llHz\n", + dep->dbg_ep_events.rxtxfifoevent, + ep_event_rate(rxtxfifoevent, dep->dbg_ep_events, + dep->dbg_ep_events_diff, delta_ms)); + seq_printf(s, "streamevent:%u @ %llHz\n", + dep->dbg_ep_events.streamevent, + ep_event_rate(streamevent, dep->dbg_ep_events, + dep->dbg_ep_events_diff, delta_ms)); + seq_printf(s, "epcmdcomplt:%u @ %llHz\n", + dep->dbg_ep_events.epcmdcomplete, + ep_event_rate(epcmdcomplete, dep->dbg_ep_events, + dep->dbg_ep_events_diff, delta_ms)); + seq_printf(s, "unknown:%u @ %llHz\n", + dep->dbg_ep_events.unknown_event, + ep_event_rate(unknown_event, dep->dbg_ep_events, + dep->dbg_ep_events_diff, delta_ms)); + seq_printf(s, "total:%u @ %llHz\n", + dep->dbg_ep_events.total, + ep_event_rate(total, dep->dbg_ep_events, + dep->dbg_ep_events_diff, delta_ms)); + + dep->dbg_ep_events_kt = now; + dep->dbg_ep_events_diff = dep->dbg_ep_events; + } + + seq_puts(s, "\n=== dbg_gadget events ==\n"); + seq_printf(s, "disconnect:%u\n reset:%u\n", + dbg_gadget_events->disconnect, dbg_gadget_events->reset); + seq_printf(s, "connect:%u\n wakeup:%u\n", + dbg_gadget_events->connect, dbg_gadget_events->wakeup); + seq_printf(s, "link_status_change:%u\n eopf:%u\n", + dbg_gadget_events->link_status_change, dbg_gadget_events->eopf); + seq_printf(s, "sof:%u\n suspend:%u\n", + dbg_gadget_events->sof, dbg_gadget_events->suspend); + seq_printf(s, "erratic_error:%u\n overflow:%u\n", + dbg_gadget_events->erratic_error, + dbg_gadget_events->overflow); + seq_printf(s, "vendor_dev_test_lmp:%u\n cmdcmplt:%u\n", + dbg_gadget_events->vendor_dev_test_lmp, + dbg_gadget_events->cmdcmplt); + seq_printf(s, "unknown_event:%u\n", dbg_gadget_events->unknown_event); + + spin_unlock_irqrestore(&dwc->lock, flags); + return 0; +} + +static int dwc3_gadget_int_events_open(struct inode *inode, struct file *f) +{ + return single_open(f, dwc3_gadget_int_events_show, inode->i_private); +} + +static const struct file_operations dwc3_gadget_int_events_fops = { + .open = dwc3_gadget_int_events_open, + .write = dwc3_gadget_int_events_store, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + void dwc3_debugfs_init(struct dwc3 *dwc) { struct dentry *root; + struct dentry *file; dwc->regset = kzalloc(sizeof(*dwc->regset), GFP_KERNEL); if (!dwc->regset) @@ -936,6 +1086,11 @@ void dwc3_debugfs_init(struct dwc3 *dwc) debugfs_create_file("link_state", S_IRUGO | S_IWUSR, root, dwc, &dwc3_link_state_fops); dwc3_debugfs_create_endpoint_dirs(dwc, root); + + file = debugfs_create_file("int_events", 0644, root, dwc, + &dwc3_gadget_int_events_fops); + if (!file) + dev_dbg(dwc->dev, "Can't create debugfs int_events\n"); } } diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 3996b9c4ff8d..2d914db9f0b0 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -1063,8 +1063,16 @@ static void dwc3_ep0_end_control_data(struct dwc3 *dwc, struct dwc3_ep *dep) static void dwc3_ep0_xfernotready(struct dwc3 *dwc, const struct dwc3_event_depevt *event) { + u8 epnum; + struct dwc3_ep *dep; + + epnum = event->endpoint_number; + dep = dwc->eps[epnum]; + switch (event->status) { case DEPEVT_STATUS_CONTROL_DATA: + dep->dbg_ep_events.control_data++; + /* * We already have a DATA transfer in the controller's cache, * if we receive a XferNotReady(DATA) we will ignore it, unless @@ -1086,6 +1094,7 @@ static void dwc3_ep0_xfernotready(struct dwc3 *dwc, break; case DEPEVT_STATUS_CONTROL_STATUS: + dep->dbg_ep_events.control_status++; if (dwc->ep0_next_event != DWC3_EP0_NRDY_STATUS) return; @@ -1117,19 +1126,32 @@ static void dwc3_ep0_xfernotready(struct dwc3 *dwc, void dwc3_ep0_interrupt(struct dwc3 *dwc, const struct dwc3_event_depevt *event) { + struct dwc3_ep *dep; + u8 epnum = event->endpoint_number; + + dep = dwc->eps[epnum]; switch (event->endpoint_event) { case DWC3_DEPEVT_XFERCOMPLETE: dwc3_ep0_xfer_complete(dwc, event); + dep->dbg_ep_events.xfercomplete++; break; case DWC3_DEPEVT_XFERNOTREADY: dwc3_ep0_xfernotready(dwc, event); + dep->dbg_ep_events.xfernotready++; break; case DWC3_DEPEVT_XFERINPROGRESS: + dep->dbg_ep_events.xferinprogress++; + break; case DWC3_DEPEVT_RXTXFIFOEVT: + dep->dbg_ep_events.rxtxfifoevent++; + break; case DWC3_DEPEVT_STREAMEVT: + dep->dbg_ep_events.streamevent++; + break; case DWC3_DEPEVT_EPCMDCMPLT: + dep->dbg_ep_events.epcmdcomplete++; break; } } diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 73f43fc18292..ae270c91293f 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2608,14 +2608,19 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, return; } + dep->dbg_ep_events.total++; + switch (event->endpoint_event) { case DWC3_DEPEVT_XFERINPROGRESS: + dep->dbg_ep_events.xferinprogress++; dwc3_gadget_endpoint_transfer_in_progress(dep, event); break; case DWC3_DEPEVT_XFERNOTREADY: + dep->dbg_ep_events.xfernotready++; dwc3_gadget_endpoint_transfer_not_ready(dep, event); break; case DWC3_DEPEVT_EPCMDCMPLT: + dep->dbg_ep_events.epcmdcomplete++; cmd = DEPEVT_PARAMETER_CMD(event->parameters); if (cmd == DWC3_DEPCMD_ENDTRANSFER) { @@ -2624,8 +2629,13 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, } break; case DWC3_DEPEVT_STREAMEVT: + dep->dbg_ep_events.streamevent++; + break; case DWC3_DEPEVT_XFERCOMPLETE: + dep->dbg_ep_events.xfercomplete++; + break; case DWC3_DEPEVT_RXTXFIFOEVT: + dep->dbg_ep_events.rxtxfifoevent++; break; } } @@ -3094,15 +3104,19 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc, switch (event->type) { case DWC3_DEVICE_EVENT_DISCONNECT: dwc3_gadget_disconnect_interrupt(dwc); + dwc->dbg_gadget_events.disconnect++; break; case DWC3_DEVICE_EVENT_RESET: dwc3_gadget_reset_interrupt(dwc); + dwc->dbg_gadget_events.reset++; break; case DWC3_DEVICE_EVENT_CONNECT_DONE: dwc3_gadget_conndone_interrupt(dwc); + dwc->dbg_gadget_events.connect++; break; case DWC3_DEVICE_EVENT_WAKEUP: dwc3_gadget_wakeup_interrupt(dwc); + dwc->dbg_gadget_events.wakeup++; break; case DWC3_DEVICE_EVENT_HIBER_REQ: if (dev_WARN_ONCE(dwc->dev, !dwc->has_hibernation, @@ -3113,10 +3127,12 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc, break; case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE: dwc3_gadget_linksts_change_interrupt(dwc, event->event_info); + dwc->dbg_gadget_events.link_status_change++; break; case DWC3_DEVICE_EVENT_EOPF: /* It changed to be suspend event for version 2.30a and above */ if (dwc->revision >= DWC3_REVISION_230A) { + dwc->dbg_gadget_events.suspend++; /* * Ignore suspend event until the gadget enters into * USB_STATE_CONFIGURED state. @@ -3127,12 +3143,20 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc, } break; case DWC3_DEVICE_EVENT_SOF: + dwc->dbg_gadget_events.sof++; + break; case DWC3_DEVICE_EVENT_ERRATIC_ERROR: + dwc->dbg_gadget_events.erratic_error++; + break; case DWC3_DEVICE_EVENT_CMD_CMPL: + dwc->dbg_gadget_events.cmdcmplt++; + break; case DWC3_DEVICE_EVENT_OVERFLOW: + dwc->dbg_gadget_events.overflow++; break; default: dev_WARN(dwc->dev, "UNKNOWN IRQ %d\n", event->type); + dwc->dbg_gadget_events.unknown_event++; } }