From 034dd340b08be1f2f0477ad16131d609f9dbd53c Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 30 Aug 2026 09:22:00 -0700 Subject: Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace Pull tracing fixes from Steven Rostedt: - Fix error output of boot instance creation failure Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead. While at it, print the error code of the failure. - Fix use-after-free for same named historgrams Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel. Free the duplicate variables along with the duplicated histogram data. - Check return value of kthread_run() in event self test The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned. - Fix race between reading trace_pipe and updating subbuffer size If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size. - Fix eventfs_inode on failure path in creation of the events directory In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false. Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation. - Check return value of kthread_run() in ftrace direct sample code The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed. - Clear user events state on fork in case of alloc failure On fork, the child gets a pointer to the parent's user events state. It makes a copy of it then updates the child's pointer to it. But if the allocation fails, the duplication function leaves the child with a pointer to its parent's descriptor. When the child cleans up its data, it will free the parent's descriptor while the parent is still using it. In the duplication function, set the child's user_event_mm to NULL before testing if the allocation succeeded, and when it exits it will not free the parent's descriptor. - Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. But the check for success expects the retry value to be non-zero and exits with an error on zero. This is the opposite result. Fix it. - Fail nicely when the remote swap_reader_page() returns an error Currently, if the swap_reader_page() of a remote buffer fails, it triggers a WARN_ON_ONCE() and continues normally. Instead, have it exit with an error and a pr_warn() print instead of a full WARNING. * tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ring-buffer: Stop remote reader update when page swap fails tracing: Fix retry exhaustion in simple ring buffer reader swap tracing/user_events: Clear copied tracing state before fork duplication samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify eventfs: Initialize ei->children and ei->list in init_ei() tracing: Fix use-after-free in trace_pipe read on sub-buffer order change tracing: Fix crash passing ERR_PTR to kthread_stop() tracing: Fix use-after-free with same-name named triggers tracing: Fix logged instance name on creation failure --- drivers/usb/host/xhci-debugfs.c | 855 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 855 insertions(+) create mode 100644 drivers/usb/host/xhci-debugfs.c (limited to 'drivers/usb/host/xhci-debugfs.c') diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c new file mode 100644 index 000000000..2aa01d99f --- /dev/null +++ b/drivers/usb/host/xhci-debugfs.c @@ -0,0 +1,855 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xhci-debugfs.c - xHCI debugfs interface + * + * Copyright (C) 2017 Intel Corporation + * + * Author: Lu Baolu + */ + +#include +#include +#include + +#include "xhci.h" +#include "xhci-debugfs.h" + +static const struct debugfs_reg32 xhci_cap_regs[] = { + dump_register(CAPLENGTH), + dump_register(HCSPARAMS1), + dump_register(HCSPARAMS2), + dump_register(HCSPARAMS3), + dump_register(HCCPARAMS1), + dump_register(DOORBELLOFF), + dump_register(RUNTIMEOFF), + dump_register(HCCPARAMS2), +}; + +static const struct debugfs_reg32 xhci_op_regs[] = { + dump_register(USBCMD), + dump_register(USBSTS), + dump_register(PAGESIZE), + dump_register(DNCTRL), + dump_register(CRCR), + dump_register(DCBAAP_LOW), + dump_register(DCBAAP_HIGH), + dump_register(CONFIG), +}; + +static const struct debugfs_reg32 xhci_runtime_regs[] = { + dump_register(MFINDEX), + dump_register(IR0_IMAN), + dump_register(IR0_IMOD), + dump_register(IR0_ERSTSZ), + dump_register(IR0_ERSTBA_LOW), + dump_register(IR0_ERSTBA_HIGH), + dump_register(IR0_ERDP_LOW), + dump_register(IR0_ERDP_HIGH), +}; + +static const struct debugfs_reg32 xhci_extcap_legsup[] = { + dump_register(EXTCAP_USBLEGSUP), + dump_register(EXTCAP_USBLEGCTLSTS), +}; + +static const struct debugfs_reg32 xhci_extcap_protocol[] = { + dump_register(EXTCAP_REVISION), + dump_register(EXTCAP_NAME), + dump_register(EXTCAP_PORTINFO), + dump_register(EXTCAP_PORTTYPE), + dump_register(EXTCAP_MANTISSA1), + dump_register(EXTCAP_MANTISSA2), + dump_register(EXTCAP_MANTISSA3), + dump_register(EXTCAP_MANTISSA4), + dump_register(EXTCAP_MANTISSA5), + dump_register(EXTCAP_MANTISSA6), +}; + +static const struct debugfs_reg32 xhci_extcap_dbc[] = { + dump_register(EXTCAP_DBC_CAPABILITY), + dump_register(EXTCAP_DBC_DOORBELL), + dump_register(EXTCAP_DBC_ERSTSIZE), + dump_register(EXTCAP_DBC_ERST_LOW), + dump_register(EXTCAP_DBC_ERST_HIGH), + dump_register(EXTCAP_DBC_ERDP_LOW), + dump_register(EXTCAP_DBC_ERDP_HIGH), + dump_register(EXTCAP_DBC_CONTROL), + dump_register(EXTCAP_DBC_STATUS), + dump_register(EXTCAP_DBC_PORTSC), + dump_register(EXTCAP_DBC_CONT_LOW), + dump_register(EXTCAP_DBC_CONT_HIGH), + dump_register(EXTCAP_DBC_DEVINFO1), + dump_register(EXTCAP_DBC_DEVINFO2), +}; + +static struct dentry *xhci_debugfs_root; + +static struct xhci_regset *xhci_debugfs_alloc_regset(struct xhci_hcd *xhci) +{ + struct xhci_regset *regset; + + regset = kzalloc_obj(*regset); + if (!regset) + return NULL; + + /* + * The allocation and free of regset are executed in order. + * We needn't a lock here. + */ + INIT_LIST_HEAD(®set->list); + list_add_tail(®set->list, &xhci->regset_list); + + return regset; +} + +static void xhci_debugfs_free_regset(struct xhci_regset *regset) +{ + if (!regset) + return; + + list_del(®set->list); + kfree(regset); +} + +__printf(6, 7) +static void xhci_debugfs_regset(struct xhci_hcd *xhci, u32 base, + const struct debugfs_reg32 *regs, + size_t nregs, struct dentry *parent, + const char *fmt, ...) +{ + struct xhci_regset *rgs; + va_list args; + struct debugfs_regset32 *regset; + struct usb_hcd *hcd = xhci_to_hcd(xhci); + + rgs = xhci_debugfs_alloc_regset(xhci); + if (!rgs) + return; + + va_start(args, fmt); + vsnprintf(rgs->name, sizeof(rgs->name), fmt, args); + va_end(args); + + regset = &rgs->regset; + regset->regs = regs; + regset->nregs = nregs; + regset->base = hcd->regs + base; + regset->dev = hcd->self.controller; + + debugfs_create_regset32((const char *)rgs->name, 0444, parent, regset); +} + +static void xhci_debugfs_extcap_regset(struct xhci_hcd *xhci, int cap_id, + const struct debugfs_reg32 *regs, + size_t n, const char *cap_name) +{ + u32 offset; + int index = 0; + size_t psic, nregs = n; + void __iomem *base = &xhci->cap_regs->hc_capbase; + + offset = xhci_find_next_ext_cap(base, 0, cap_id); + while (offset) { + if (cap_id == XHCI_EXT_CAPS_PROTOCOL) { + psic = XHCI_EXT_PORT_PSIC(readl(base + offset + 8)); + nregs = min(4 + psic, n); + } + + xhci_debugfs_regset(xhci, offset, regs, nregs, + xhci->debugfs_root, "%s:%02d", + cap_name, index); + offset = xhci_find_next_ext_cap(base, offset, cap_id); + index++; + } +} + +static int xhci_ring_enqueue_show(struct seq_file *s, void *unused) +{ + dma_addr_t dma; + struct xhci_ring *ring = *(struct xhci_ring **)s->private; + + dma = xhci_trb_virt_to_dma(ring->enq_seg, ring->enqueue); + seq_printf(s, "%pad\n", &dma); + + return 0; +} + +static int xhci_ring_dequeue_show(struct seq_file *s, void *unused) +{ + dma_addr_t dma; + struct xhci_ring *ring = *(struct xhci_ring **)s->private; + + dma = xhci_trb_virt_to_dma(ring->deq_seg, ring->dequeue); + seq_printf(s, "%pad\n", &dma); + + return 0; +} + +static int xhci_ring_cycle_show(struct seq_file *s, void *unused) +{ + struct xhci_ring *ring = *(struct xhci_ring **)s->private; + + seq_printf(s, "%d\n", ring->cycle_state); + + return 0; +} + +static void xhci_ring_dump_segment(struct seq_file *s, + struct xhci_segment *seg) +{ + int i; + dma_addr_t dma; + union xhci_trb *trb; + char str[XHCI_MSG_MAX]; + + for (i = 0; i < TRBS_PER_SEGMENT; i++) { + trb = &seg->trbs[i]; + dma = seg->dma + i * sizeof(*trb); + seq_printf(s, "%2u %pad: %s\n", seg->num, &dma, + xhci_decode_trb(str, XHCI_MSG_MAX, le32_to_cpu(trb->generic.field[0]), + le32_to_cpu(trb->generic.field[1]), + le32_to_cpu(trb->generic.field[2]), + le32_to_cpu(trb->generic.field[3]))); + } +} + +static int xhci_ring_trb_show(struct seq_file *s, void *unused) +{ + struct xhci_ring *ring = *(struct xhci_ring **)s->private; + struct xhci_segment *seg = ring->first_seg; + + xhci_for_each_ring_seg(ring->first_seg, seg) + xhci_ring_dump_segment(s, seg); + + return 0; +} + +static struct xhci_file_map ring_files[] = { + {"enqueue", xhci_ring_enqueue_show, }, + {"dequeue", xhci_ring_dequeue_show, }, + {"cycle", xhci_ring_cycle_show, }, + {"trbs", xhci_ring_trb_show, }, +}; + +static int xhci_ring_open(struct inode *inode, struct file *file) +{ + const struct xhci_file_map *f_map = debugfs_get_aux(file); + + return single_open(file, f_map->show, inode->i_private); +} + +static const struct file_operations xhci_ring_fops = { + .open = xhci_ring_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int xhci_slot_context_show(struct seq_file *s, void *unused) +{ + struct xhci_hcd *xhci; + struct xhci_slot_ctx *slot_ctx; + struct xhci_slot_priv *priv = s->private; + struct xhci_virt_device *dev = priv->dev; + char str[XHCI_MSG_MAX]; + + xhci = hcd_to_xhci(bus_to_hcd(dev->udev->bus)); + slot_ctx = xhci_get_slot_ctx(xhci, dev->out_ctx); + seq_printf(s, "%pad: %s\n", &dev->out_ctx->dma, + xhci_decode_slot_context(str, + le32_to_cpu(slot_ctx->dev_info), + le32_to_cpu(slot_ctx->dev_info2), + le32_to_cpu(slot_ctx->tt_info), + le32_to_cpu(slot_ctx->dev_state))); + + return 0; +} + +static int xhci_endpoint_context_show(struct seq_file *s, void *unused) +{ + int ep_index; + dma_addr_t dma; + struct xhci_hcd *xhci; + struct xhci_ep_ctx *ep_ctx; + struct xhci_slot_priv *priv = s->private; + struct xhci_virt_device *dev = priv->dev; + char str[XHCI_MSG_MAX]; + + xhci = hcd_to_xhci(bus_to_hcd(dev->udev->bus)); + + for (ep_index = 0; ep_index < 31; ep_index++) { + ep_ctx = xhci_get_ep_ctx(xhci, dev->out_ctx, ep_index); + dma = dev->out_ctx->dma + (ep_index + 1) * CTX_SIZE(xhci->hcc_params); + seq_printf(s, "%pad: %s, virt_state:%#x\n", &dma, + xhci_decode_ep_context(str, + le32_to_cpu(ep_ctx->ep_info), + le32_to_cpu(ep_ctx->ep_info2), + le64_to_cpu(ep_ctx->deq), + le32_to_cpu(ep_ctx->tx_info)), + dev->eps[ep_index].ep_state); + } + + return 0; +} + +static int xhci_device_name_show(struct seq_file *s, void *unused) +{ + struct xhci_slot_priv *priv = s->private; + struct xhci_virt_device *dev = priv->dev; + + seq_printf(s, "%s\n", dev_name(&dev->udev->dev)); + + return 0; +} + +static struct xhci_file_map context_files[] = { + {"name", xhci_device_name_show, }, + {"slot-context", xhci_slot_context_show, }, + {"ep-context", xhci_endpoint_context_show, }, +}; + +static int xhci_context_open(struct inode *inode, struct file *file) +{ + const struct xhci_file_map *f_map = debugfs_get_aux(file); + + return single_open(file, f_map->show, inode->i_private); +} + +static const struct file_operations xhci_context_fops = { + .open = xhci_context_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + + + +static int xhci_portsc_show(struct seq_file *s, void *unused) +{ + struct xhci_port *port = s->private; + u32 portsc; + char str[XHCI_MSG_MAX]; + + portsc = xhci_portsc_readl(port); + seq_printf(s, "%s\n", xhci_decode_portsc(str, portsc)); + + return 0; +} + +static int xhci_port_open(struct inode *inode, struct file *file) +{ + return single_open(file, xhci_portsc_show, inode->i_private); +} + +static ssize_t xhci_port_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct xhci_port *port = s->private; + struct xhci_hcd *xhci = hcd_to_xhci(port->rhub->hcd); + char buf[32]; + u32 portsc; + unsigned long flags; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "compliance", 10)) { + /* If CTC is clear, compliance is enabled by default */ + if (!(xhci->hcc_params2 & HCC2_CTC)) + return count; + spin_lock_irqsave(&xhci->lock, flags); + /* compliance mode can only be enabled on ports in RxDetect */ + portsc = xhci_portsc_readl(port); + if ((portsc & PORT_PLS_MASK) != XDEV_RXDETECT) { + spin_unlock_irqrestore(&xhci->lock, flags); + return -EPERM; + } + portsc = xhci_port_state_to_neutral(portsc); + portsc &= ~PORT_PLS_MASK; + portsc |= PORT_LINK_STROBE | XDEV_COMP_MODE; + xhci_portsc_writel(port, portsc); + spin_unlock_irqrestore(&xhci->lock, flags); + } else { + return -EINVAL; + } + return count; +} + +static const struct file_operations port_fops = { + .open = xhci_port_open, + .write = xhci_port_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int xhci_portli_show(struct seq_file *s, void *unused) +{ + struct xhci_port *port = s->private; + struct xhci_hcd *xhci; + u32 portli; + + portli = readl(&port->port_reg->portli); + + /* port without protocol capability isn't added to a roothub */ + if (!port->rhub) { + seq_printf(s, "0x%08x\n", portli); + return 0; + } + + xhci = hcd_to_xhci(port->rhub->hcd); + + /* PORTLI fields are valid if port is a USB3 or eUSB2V2 port */ + if (port->rhub == &xhci->usb3_rhub) + seq_printf(s, "0x%08x LEC=%u RLC=%u TLC=%u\n", portli, + PORT_LEC(portli), PORT_RX_LANES(portli), PORT_TX_LANES(portli)); + else if (xhci->hcc_params2 & HCC2_E2V2C) + seq_printf(s, "0x%08x RDR=%u TDR=%u\n", portli, + PORTLI_RDR(portli), PORTLI_TDR(portli)); + else + seq_printf(s, "0x%08x RsvdP\n", portli); + + return 0; +} + +static int xhci_portli_open(struct inode *inode, struct file *file) +{ + return single_open(file, xhci_portli_show, inode->i_private); +} + +static const struct file_operations portli_fops = { + .open = xhci_portli_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void xhci_debugfs_create_files(struct xhci_hcd *xhci, + struct xhci_file_map *files, + size_t nentries, void *data, + struct dentry *parent, + const struct file_operations *fops) +{ + int i; + + for (i = 0; i < nentries; i++) + debugfs_create_file_aux(files[i].name, 0444, parent, + data, &files[i], fops); +} + +static struct dentry *xhci_debugfs_create_ring_dir(struct xhci_hcd *xhci, + struct xhci_ring **ring, + const char *name, + struct dentry *parent) +{ + struct dentry *dir; + + dir = debugfs_create_dir(name, parent); + xhci_debugfs_create_files(xhci, ring_files, ARRAY_SIZE(ring_files), + ring, dir, &xhci_ring_fops); + + return dir; +} + +static void xhci_debugfs_create_context_files(struct xhci_hcd *xhci, + struct dentry *parent, + int slot_id) +{ + struct xhci_virt_device *dev = xhci->devs[slot_id]; + + xhci_debugfs_create_files(xhci, context_files, + ARRAY_SIZE(context_files), + dev->debugfs_private, + parent, &xhci_context_fops); +} + +void xhci_debugfs_create_endpoint(struct xhci_hcd *xhci, + struct xhci_virt_device *dev, + int ep_index) +{ + struct xhci_ep_priv *epriv; + struct xhci_slot_priv *spriv = dev->debugfs_private; + + if (!spriv) + return; + + if (spriv->eps[ep_index]) + return; + + epriv = kzalloc_obj(*epriv); + if (!epriv) + return; + + epriv->show_ring = dev->eps[ep_index].ring; + + snprintf(epriv->name, sizeof(epriv->name), "ep%02d", ep_index); + epriv->root = xhci_debugfs_create_ring_dir(xhci, + &epriv->show_ring, + epriv->name, + spriv->root); + spriv->eps[ep_index] = epriv; +} + +void xhci_debugfs_remove_endpoint(struct xhci_hcd *xhci, + struct xhci_virt_device *dev, + int ep_index) +{ + struct xhci_ep_priv *epriv; + struct xhci_slot_priv *spriv = dev->debugfs_private; + + if (!spriv || !spriv->eps[ep_index]) + return; + + epriv = spriv->eps[ep_index]; + debugfs_remove_recursive(epriv->root); + spriv->eps[ep_index] = NULL; + kfree(epriv); +} + +static int xhci_stream_id_show(struct seq_file *s, void *unused) +{ + struct xhci_ep_priv *epriv = s->private; + + if (!epriv->stream_info) + return -EPERM; + + seq_printf(s, "Show stream ID %d trb ring, supported [1 - %d]\n", + epriv->stream_id, epriv->stream_info->num_streams - 1); + + return 0; +} + +static int xhci_stream_id_open(struct inode *inode, struct file *file) +{ + return single_open(file, xhci_stream_id_show, inode->i_private); +} + +static ssize_t xhci_stream_id_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct xhci_ep_priv *epriv = s->private; + int ret; + u16 stream_id; /* MaxPStreams + 1 <= 16 */ + + if (!epriv->stream_info) + return -EPERM; + + /* Decimal number */ + ret = kstrtou16_from_user(ubuf, count, 10, &stream_id); + if (ret) + return ret; + + if (stream_id == 0 || stream_id >= epriv->stream_info->num_streams) + return -EINVAL; + + epriv->stream_id = stream_id; + epriv->show_ring = epriv->stream_info->stream_rings[stream_id]; + + return count; +} + +static const struct file_operations stream_id_fops = { + .open = xhci_stream_id_open, + .write = xhci_stream_id_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int xhci_stream_context_array_show(struct seq_file *s, void *unused) +{ + struct xhci_ep_priv *epriv = s->private; + struct xhci_stream_ctx *stream_ctx; + dma_addr_t dma; + int id; + + if (!epriv->stream_info) + return -EPERM; + + seq_printf(s, "Allocated %d streams and %d stream context array entries\n", + epriv->stream_info->num_streams, + epriv->stream_info->num_stream_ctxs); + + for (id = 0; id < epriv->stream_info->num_stream_ctxs; id++) { + stream_ctx = epriv->stream_info->stream_ctx_array + id; + dma = epriv->stream_info->ctx_array_dma + id * 16; + if (id < epriv->stream_info->num_streams) + seq_printf(s, "%pad stream id %d deq %016llx\n", &dma, + id, le64_to_cpu(stream_ctx->stream_ring)); + else + seq_printf(s, "%pad stream context entry not used deq %016llx\n", + &dma, le64_to_cpu(stream_ctx->stream_ring)); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(xhci_stream_context_array); + +void xhci_debugfs_create_stream_files(struct xhci_hcd *xhci, + struct xhci_virt_device *dev, + int ep_index) +{ + struct xhci_slot_priv *spriv = dev->debugfs_private; + struct xhci_ep_priv *epriv; + + if (!spriv || !spriv->eps[ep_index] || + !dev->eps[ep_index].stream_info) + return; + + epriv = spriv->eps[ep_index]; + epriv->stream_info = dev->eps[ep_index].stream_info; + + /* Show trb ring of stream ID 1 by default */ + epriv->stream_id = 1; + epriv->show_ring = epriv->stream_info->stream_rings[1]; + debugfs_create_file("stream_id", 0644, + epriv->root, epriv, + &stream_id_fops); + debugfs_create_file("stream_context_array", 0444, + epriv->root, epriv, + &xhci_stream_context_array_fops); +} + +void xhci_debugfs_create_slot(struct xhci_hcd *xhci, int slot_id) +{ + struct xhci_slot_priv *priv; + struct xhci_virt_device *dev = xhci->devs[slot_id]; + + priv = kzalloc_obj(*priv); + if (!priv) + return; + + snprintf(priv->name, sizeof(priv->name), "%02d", slot_id); + priv->root = debugfs_create_dir(priv->name, xhci->debugfs_slots); + priv->dev = dev; + dev->debugfs_private = priv; + + xhci_debugfs_create_ring_dir(xhci, &dev->eps[0].ring, + "ep00", priv->root); + + xhci_debugfs_create_context_files(xhci, priv->root, slot_id); +} + +void xhci_debugfs_remove_slot(struct xhci_hcd *xhci, int slot_id) +{ + int i; + struct xhci_slot_priv *priv; + struct xhci_virt_device *dev = xhci->devs[slot_id]; + + if (!dev || !dev->debugfs_private) + return; + + priv = dev->debugfs_private; + + debugfs_remove_recursive(priv->root); + + for (i = 0; i < 31; i++) + kfree(priv->eps[i]); + + kfree(priv); + dev->debugfs_private = NULL; +} + +static void xhci_debugfs_create_ports(struct xhci_hcd *xhci, + struct dentry *parent) +{ + char port_name[8]; + struct xhci_port *port; + struct dentry *dir; + + parent = debugfs_create_dir("ports", parent); + + for (int i = 0; i < xhci->max_ports; i++) { + scnprintf(port_name, sizeof(port_name), "port%02d", i + 1); + dir = debugfs_create_dir(port_name, parent); + port = &xhci->hw_ports[i]; + debugfs_create_file("portsc", 0644, dir, port, &port_fops); + debugfs_create_file("portli", 0444, dir, port, &portli_fops); + } +} + +static int xhci_port_bw_show(struct xhci_hcd *xhci, u8 dev_speed, + struct seq_file *s) +{ + unsigned int i; + int ret; + struct xhci_container_ctx *ctx; + struct usb_hcd *hcd = xhci_to_hcd(xhci); + struct device *dev = hcd->self.controller; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) + return ret; + + ctx = xhci_alloc_port_bw_ctx(xhci, 0); + if (!ctx) { + pm_runtime_put_sync(dev); + return -ENOMEM; + } + + /* get roothub port bandwidth */ + ret = xhci_get_port_bandwidth(xhci, ctx, dev_speed); + if (ret) + goto err_out; + + /* print all roothub ports available bandwidth + * refer to xhci rev1_2 protocol 6.2.6 , byte 0 is reserved + */ + for (i = 1; i <= xhci->max_ports; i++) + seq_printf(s, "port[%d] available bw: %d%%.\n", i, + ctx->bytes[i]); +err_out: + if (ret == -EIO) { + seq_puts(s, "Get Port Bandwidth failed\n"); + ret = 0; + } + pm_runtime_put_sync(dev); + xhci_free_port_bw_ctx(xhci, ctx); + return ret; +} + +static int xhci_ss_bw_show(struct seq_file *s, void *unused) +{ + int ret; + struct xhci_hcd *xhci = (struct xhci_hcd *)s->private; + + ret = xhci_port_bw_show(xhci, DEV_PORT_SPEED(XDEV_SS), s); + return ret; +} + +static int xhci_hs_bw_show(struct seq_file *s, void *unused) +{ + int ret; + struct xhci_hcd *xhci = (struct xhci_hcd *)s->private; + + ret = xhci_port_bw_show(xhci, DEV_PORT_SPEED(XDEV_HS), s); + return ret; +} + +static int xhci_fs_bw_show(struct seq_file *s, void *unused) +{ + int ret; + struct xhci_hcd *xhci = (struct xhci_hcd *)s->private; + + ret = xhci_port_bw_show(xhci, DEV_PORT_SPEED(XDEV_FS), s); + return ret; +} + +static struct xhci_file_map bw_context_files[] = { + {"SS_BW", xhci_ss_bw_show, }, + {"HS_BW", xhci_hs_bw_show, }, + {"FS_BW", xhci_fs_bw_show, }, +}; + +static int bw_context_open(struct inode *inode, struct file *file) +{ + int i; + struct xhci_file_map *f_map; + const char *file_name = file_dentry(file)->d_iname; + + for (i = 0; i < ARRAY_SIZE(bw_context_files); i++) { + f_map = &bw_context_files[i]; + + if (strcmp(f_map->name, file_name) == 0) + break; + } + + return single_open(file, f_map->show, inode->i_private); +} + +static const struct file_operations bw_fops = { + .open = bw_context_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void xhci_debugfs_create_bandwidth(struct xhci_hcd *xhci, + struct dentry *parent) +{ + parent = debugfs_create_dir("port_bandwidth", parent); + + xhci_debugfs_create_files(xhci, bw_context_files, + ARRAY_SIZE(bw_context_files), + xhci, + parent, &bw_fops); +} + +void xhci_debugfs_init(struct xhci_hcd *xhci) +{ + struct device *dev = xhci_to_hcd(xhci)->self.controller; + + xhci->debugfs_root = debugfs_create_dir(dev_name(dev), + xhci_debugfs_root); + + INIT_LIST_HEAD(&xhci->regset_list); + + xhci_debugfs_regset(xhci, + 0, + xhci_cap_regs, ARRAY_SIZE(xhci_cap_regs), + xhci->debugfs_root, "reg-cap"); + + xhci_debugfs_regset(xhci, + FIELD_GET(HC_LENGTH, readl(&xhci->cap_regs->hc_capbase)), + xhci_op_regs, ARRAY_SIZE(xhci_op_regs), + xhci->debugfs_root, "reg-op"); + + xhci_debugfs_regset(xhci, + readl(&xhci->cap_regs->run_regs_off) & RTSOFF_MASK, + xhci_runtime_regs, ARRAY_SIZE(xhci_runtime_regs), + xhci->debugfs_root, "reg-runtime"); + + xhci_debugfs_extcap_regset(xhci, XHCI_EXT_CAPS_LEGACY, + xhci_extcap_legsup, + ARRAY_SIZE(xhci_extcap_legsup), + "reg-ext-legsup"); + + xhci_debugfs_extcap_regset(xhci, XHCI_EXT_CAPS_PROTOCOL, + xhci_extcap_protocol, + ARRAY_SIZE(xhci_extcap_protocol), + "reg-ext-protocol"); + + xhci_debugfs_extcap_regset(xhci, XHCI_EXT_CAPS_DEBUG, + xhci_extcap_dbc, + ARRAY_SIZE(xhci_extcap_dbc), + "reg-ext-dbc"); + + xhci_debugfs_create_ring_dir(xhci, &xhci->cmd_ring, + "command-ring", + xhci->debugfs_root); + + xhci_debugfs_create_ring_dir(xhci, &xhci->interrupters[0]->event_ring, + "event-ring", + xhci->debugfs_root); + + xhci->debugfs_slots = debugfs_create_dir("devices", xhci->debugfs_root); + + xhci_debugfs_create_ports(xhci, xhci->debugfs_root); + + xhci_debugfs_create_bandwidth(xhci, xhci->debugfs_root); +} + +void xhci_debugfs_exit(struct xhci_hcd *xhci) +{ + struct xhci_regset *rgs, *tmp; + + debugfs_remove_recursive(xhci->debugfs_root); + xhci->debugfs_root = NULL; + xhci->debugfs_slots = NULL; + + list_for_each_entry_safe(rgs, tmp, &xhci->regset_list, list) + xhci_debugfs_free_regset(rgs); +} + +void __init xhci_debugfs_create_root(void) +{ + xhci_debugfs_root = debugfs_create_dir("xhci", usb_debug_root); +} + +void __exit xhci_debugfs_remove_root(void) +{ + debugfs_remove_recursive(xhci_debugfs_root); + xhci_debugfs_root = NULL; +} -- cgit v1.3.1