From 82dc6471a5513938f130e2ddd28424145884bcac Mon Sep 17 00:00:00 2001 From: Jack Pham Date: Thu, 18 Jan 2018 14:19:38 -0800 Subject: [PATCH] usb: dwc3: Keep track of interrupt statistics This change adds debug support to log all received different events with endpoint0, other endpoints and gadget events. It tracks these events per endpoint and displays the same. For each of the endpoint event counters, add the rate (in Hz) at which it occurs in order to better analyze performance and aid in optimization. How to use: - Mount debugfs - To see received all dwc3 events/interrupts cat /sys/kernel/debug/.dwc3/int_events - To clear all received dwc3 events/interrupts echo 0 > /sys/kernel/debug/.dwc3/int_events Change-Id: Ibf5f3ee57f69c87f94f55a58f50792075be24fbb Signed-off-by: Gilad Broner Signed-off-by: Mayank Rana Signed-off-by: Jack Pham --- drivers/usb/dwc3/core.h | 37 +++++++++ drivers/usb/dwc3/debugfs.c | 155 +++++++++++++++++++++++++++++++++++++ drivers/usb/dwc3/ep0.c | 22 ++++++ drivers/usb/dwc3/gadget.c | 24 ++++++ 4 files changed, 238 insertions(+) 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++; } }