| 1 | // SPDX-License-Identifier: GPL-2.0 | 
|---|
| 2 | /* | 
|---|
| 3 | * xhci-debugfs.c - xHCI debugfs interface | 
|---|
| 4 | * | 
|---|
| 5 | * Copyright (C) 2017 Intel Corporation | 
|---|
| 6 | * | 
|---|
| 7 | * Author: Lu Baolu <baolu.lu@linux.intel.com> | 
|---|
| 8 | */ | 
|---|
| 9 |  | 
|---|
| 10 | #include <linux/slab.h> | 
|---|
| 11 | #include <linux/uaccess.h> | 
|---|
| 12 |  | 
|---|
| 13 | #include "xhci.h" | 
|---|
| 14 | #include "xhci-debugfs.h" | 
|---|
| 15 |  | 
|---|
| 16 | static const struct debugfs_reg32 xhci_cap_regs[] = { | 
|---|
| 17 | dump_register(CAPLENGTH), | 
|---|
| 18 | dump_register(HCSPARAMS1), | 
|---|
| 19 | dump_register(HCSPARAMS2), | 
|---|
| 20 | dump_register(HCSPARAMS3), | 
|---|
| 21 | dump_register(HCCPARAMS1), | 
|---|
| 22 | dump_register(DOORBELLOFF), | 
|---|
| 23 | dump_register(RUNTIMEOFF), | 
|---|
| 24 | dump_register(HCCPARAMS2), | 
|---|
| 25 | }; | 
|---|
| 26 |  | 
|---|
| 27 | static const struct debugfs_reg32 xhci_op_regs[] = { | 
|---|
| 28 | dump_register(USBCMD), | 
|---|
| 29 | dump_register(USBSTS), | 
|---|
| 30 | dump_register(PAGESIZE), | 
|---|
| 31 | dump_register(DNCTRL), | 
|---|
| 32 | dump_register(CRCR), | 
|---|
| 33 | dump_register(DCBAAP_LOW), | 
|---|
| 34 | dump_register(DCBAAP_HIGH), | 
|---|
| 35 | dump_register(CONFIG), | 
|---|
| 36 | }; | 
|---|
| 37 |  | 
|---|
| 38 | static const struct debugfs_reg32 xhci_runtime_regs[] = { | 
|---|
| 39 | dump_register(MFINDEX), | 
|---|
| 40 | dump_register(IR0_IMAN), | 
|---|
| 41 | dump_register(IR0_IMOD), | 
|---|
| 42 | dump_register(IR0_ERSTSZ), | 
|---|
| 43 | dump_register(IR0_ERSTBA_LOW), | 
|---|
| 44 | dump_register(IR0_ERSTBA_HIGH), | 
|---|
| 45 | dump_register(IR0_ERDP_LOW), | 
|---|
| 46 | dump_register(IR0_ERDP_HIGH), | 
|---|
| 47 | }; | 
|---|
| 48 |  | 
|---|
| 49 | static const struct debugfs_reg32 xhci_extcap_legsup[] = { | 
|---|
| 50 | dump_register(EXTCAP_USBLEGSUP), | 
|---|
| 51 | dump_register(EXTCAP_USBLEGCTLSTS), | 
|---|
| 52 | }; | 
|---|
| 53 |  | 
|---|
| 54 | static const struct debugfs_reg32 xhci_extcap_protocol[] = { | 
|---|
| 55 | dump_register(EXTCAP_REVISION), | 
|---|
| 56 | dump_register(EXTCAP_NAME), | 
|---|
| 57 | dump_register(EXTCAP_PORTINFO), | 
|---|
| 58 | dump_register(EXTCAP_PORTTYPE), | 
|---|
| 59 | dump_register(EXTCAP_MANTISSA1), | 
|---|
| 60 | dump_register(EXTCAP_MANTISSA2), | 
|---|
| 61 | dump_register(EXTCAP_MANTISSA3), | 
|---|
| 62 | dump_register(EXTCAP_MANTISSA4), | 
|---|
| 63 | dump_register(EXTCAP_MANTISSA5), | 
|---|
| 64 | dump_register(EXTCAP_MANTISSA6), | 
|---|
| 65 | }; | 
|---|
| 66 |  | 
|---|
| 67 | static const struct debugfs_reg32 xhci_extcap_dbc[] = { | 
|---|
| 68 | dump_register(EXTCAP_DBC_CAPABILITY), | 
|---|
| 69 | dump_register(EXTCAP_DBC_DOORBELL), | 
|---|
| 70 | dump_register(EXTCAP_DBC_ERSTSIZE), | 
|---|
| 71 | dump_register(EXTCAP_DBC_ERST_LOW), | 
|---|
| 72 | dump_register(EXTCAP_DBC_ERST_HIGH), | 
|---|
| 73 | dump_register(EXTCAP_DBC_ERDP_LOW), | 
|---|
| 74 | dump_register(EXTCAP_DBC_ERDP_HIGH), | 
|---|
| 75 | dump_register(EXTCAP_DBC_CONTROL), | 
|---|
| 76 | dump_register(EXTCAP_DBC_STATUS), | 
|---|
| 77 | dump_register(EXTCAP_DBC_PORTSC), | 
|---|
| 78 | dump_register(EXTCAP_DBC_CONT_LOW), | 
|---|
| 79 | dump_register(EXTCAP_DBC_CONT_HIGH), | 
|---|
| 80 | dump_register(EXTCAP_DBC_DEVINFO1), | 
|---|
| 81 | dump_register(EXTCAP_DBC_DEVINFO2), | 
|---|
| 82 | }; | 
|---|
| 83 |  | 
|---|
| 84 | static struct dentry *xhci_debugfs_root; | 
|---|
| 85 |  | 
|---|
| 86 | static struct xhci_regset *xhci_debugfs_alloc_regset(struct xhci_hcd *xhci) | 
|---|
| 87 | { | 
|---|
| 88 | struct xhci_regset	*regset; | 
|---|
| 89 |  | 
|---|
| 90 | regset = kzalloc(sizeof(*regset), GFP_KERNEL); | 
|---|
| 91 | if (!regset) | 
|---|
| 92 | return NULL; | 
|---|
| 93 |  | 
|---|
| 94 | /* | 
|---|
| 95 | * The allocation and free of regset are executed in order. | 
|---|
| 96 | * We needn't a lock here. | 
|---|
| 97 | */ | 
|---|
| 98 | INIT_LIST_HEAD(list: ®set->list); | 
|---|
| 99 | list_add_tail(new: ®set->list, head: &xhci->regset_list); | 
|---|
| 100 |  | 
|---|
| 101 | return regset; | 
|---|
| 102 | } | 
|---|
| 103 |  | 
|---|
| 104 | static void xhci_debugfs_free_regset(struct xhci_regset *regset) | 
|---|
| 105 | { | 
|---|
| 106 | if (!regset) | 
|---|
| 107 | return; | 
|---|
| 108 |  | 
|---|
| 109 | list_del(entry: ®set->list); | 
|---|
| 110 | kfree(objp: regset); | 
|---|
| 111 | } | 
|---|
| 112 |  | 
|---|
| 113 | __printf(6, 7) | 
|---|
| 114 | static void xhci_debugfs_regset(struct xhci_hcd *xhci, u32 base, | 
|---|
| 115 | const struct debugfs_reg32 *regs, | 
|---|
| 116 | size_t nregs, struct dentry *parent, | 
|---|
| 117 | const char *fmt, ...) | 
|---|
| 118 | { | 
|---|
| 119 | struct xhci_regset	*rgs; | 
|---|
| 120 | va_list			args; | 
|---|
| 121 | struct debugfs_regset32	*regset; | 
|---|
| 122 | struct usb_hcd		*hcd = xhci_to_hcd(xhci); | 
|---|
| 123 |  | 
|---|
| 124 | rgs = xhci_debugfs_alloc_regset(xhci); | 
|---|
| 125 | if (!rgs) | 
|---|
| 126 | return; | 
|---|
| 127 |  | 
|---|
| 128 | va_start(args, fmt); | 
|---|
| 129 | vsnprintf(buf: rgs->name, size: sizeof(rgs->name), fmt, args); | 
|---|
| 130 | va_end(args); | 
|---|
| 131 |  | 
|---|
| 132 | regset = &rgs->regset; | 
|---|
| 133 | regset->regs = regs; | 
|---|
| 134 | regset->nregs = nregs; | 
|---|
| 135 | regset->base = hcd->regs + base; | 
|---|
| 136 | regset->dev = hcd->self.controller; | 
|---|
| 137 |  | 
|---|
| 138 | debugfs_create_regset32(name: (const char *)rgs->name, mode: 0444, parent, regset); | 
|---|
| 139 | } | 
|---|
| 140 |  | 
|---|
| 141 | static void xhci_debugfs_extcap_regset(struct xhci_hcd *xhci, int cap_id, | 
|---|
| 142 | const struct debugfs_reg32 *regs, | 
|---|
| 143 | size_t n, const char *cap_name) | 
|---|
| 144 | { | 
|---|
| 145 | u32			offset; | 
|---|
| 146 | int			index = 0; | 
|---|
| 147 | size_t			psic, nregs = n; | 
|---|
| 148 | void __iomem		*base = &xhci->cap_regs->hc_capbase; | 
|---|
| 149 |  | 
|---|
| 150 | offset = xhci_find_next_ext_cap(base, start: 0, id: cap_id); | 
|---|
| 151 | while (offset) { | 
|---|
| 152 | if (cap_id == XHCI_EXT_CAPS_PROTOCOL) { | 
|---|
| 153 | psic = XHCI_EXT_PORT_PSIC(readl(base + offset + 8)); | 
|---|
| 154 | nregs = min(4 + psic, n); | 
|---|
| 155 | } | 
|---|
| 156 |  | 
|---|
| 157 | xhci_debugfs_regset(xhci, base: offset, regs, nregs, | 
|---|
| 158 | parent: xhci->debugfs_root, fmt: "%s:%02d", | 
|---|
| 159 | cap_name, index); | 
|---|
| 160 | offset = xhci_find_next_ext_cap(base, start: offset, id: cap_id); | 
|---|
| 161 | index++; | 
|---|
| 162 | } | 
|---|
| 163 | } | 
|---|
| 164 |  | 
|---|
| 165 | static int xhci_ring_enqueue_show(struct seq_file *s, void *unused) | 
|---|
| 166 | { | 
|---|
| 167 | dma_addr_t		dma; | 
|---|
| 168 | struct xhci_ring	*ring = *(struct xhci_ring **)s->private; | 
|---|
| 169 |  | 
|---|
| 170 | dma = xhci_trb_virt_to_dma(seg: ring->enq_seg, trb: ring->enqueue); | 
|---|
| 171 | seq_printf(m: s, fmt: "%pad\n", &dma); | 
|---|
| 172 |  | 
|---|
| 173 | return 0; | 
|---|
| 174 | } | 
|---|
| 175 |  | 
|---|
| 176 | static int xhci_ring_dequeue_show(struct seq_file *s, void *unused) | 
|---|
| 177 | { | 
|---|
| 178 | dma_addr_t		dma; | 
|---|
| 179 | struct xhci_ring	*ring = *(struct xhci_ring **)s->private; | 
|---|
| 180 |  | 
|---|
| 181 | dma = xhci_trb_virt_to_dma(seg: ring->deq_seg, trb: ring->dequeue); | 
|---|
| 182 | seq_printf(m: s, fmt: "%pad\n", &dma); | 
|---|
| 183 |  | 
|---|
| 184 | return 0; | 
|---|
| 185 | } | 
|---|
| 186 |  | 
|---|
| 187 | static int xhci_ring_cycle_show(struct seq_file *s, void *unused) | 
|---|
| 188 | { | 
|---|
| 189 | struct xhci_ring	*ring = *(struct xhci_ring **)s->private; | 
|---|
| 190 |  | 
|---|
| 191 | seq_printf(m: s, fmt: "%d\n", ring->cycle_state); | 
|---|
| 192 |  | 
|---|
| 193 | return 0; | 
|---|
| 194 | } | 
|---|
| 195 |  | 
|---|
| 196 | static void xhci_ring_dump_segment(struct seq_file *s, | 
|---|
| 197 | struct xhci_segment *seg) | 
|---|
| 198 | { | 
|---|
| 199 | int			i; | 
|---|
| 200 | dma_addr_t		dma; | 
|---|
| 201 | union xhci_trb		*trb; | 
|---|
| 202 | char			str[XHCI_MSG_MAX]; | 
|---|
| 203 |  | 
|---|
| 204 | for (i = 0; i < TRBS_PER_SEGMENT; i++) { | 
|---|
| 205 | trb = &seg->trbs[i]; | 
|---|
| 206 | dma = seg->dma + i * sizeof(*trb); | 
|---|
| 207 | seq_printf(m: s, fmt: "%2u %pad: %s\n", seg->num, &dma, | 
|---|
| 208 | xhci_decode_trb(str, XHCI_MSG_MAX, le32_to_cpu(trb->generic.field[0]), | 
|---|
| 209 | le32_to_cpu(trb->generic.field[1]), | 
|---|
| 210 | le32_to_cpu(trb->generic.field[2]), | 
|---|
| 211 | le32_to_cpu(trb->generic.field[3]))); | 
|---|
| 212 | } | 
|---|
| 213 | } | 
|---|
| 214 |  | 
|---|
| 215 | static int xhci_ring_trb_show(struct seq_file *s, void *unused) | 
|---|
| 216 | { | 
|---|
| 217 | struct xhci_ring	*ring = *(struct xhci_ring **)s->private; | 
|---|
| 218 | struct xhci_segment	*seg = ring->first_seg; | 
|---|
| 219 |  | 
|---|
| 220 | xhci_for_each_ring_seg(ring->first_seg, seg) | 
|---|
| 221 | xhci_ring_dump_segment(s, seg); | 
|---|
| 222 |  | 
|---|
| 223 | return 0; | 
|---|
| 224 | } | 
|---|
| 225 |  | 
|---|
| 226 | static struct xhci_file_map ring_files[] = { | 
|---|
| 227 | { "enqueue",		xhci_ring_enqueue_show, }, | 
|---|
| 228 | { "dequeue",		xhci_ring_dequeue_show, }, | 
|---|
| 229 | { "cycle",		xhci_ring_cycle_show, }, | 
|---|
| 230 | { "trbs",		xhci_ring_trb_show, }, | 
|---|
| 231 | }; | 
|---|
| 232 |  | 
|---|
| 233 | static int xhci_ring_open(struct inode *inode, struct file *file) | 
|---|
| 234 | { | 
|---|
| 235 | const struct xhci_file_map *f_map = debugfs_get_aux(file); | 
|---|
| 236 |  | 
|---|
| 237 | return single_open(file, f_map->show, inode->i_private); | 
|---|
| 238 | } | 
|---|
| 239 |  | 
|---|
| 240 | static const struct file_operations xhci_ring_fops = { | 
|---|
| 241 | .open			= xhci_ring_open, | 
|---|
| 242 | .read			= seq_read, | 
|---|
| 243 | .llseek			= seq_lseek, | 
|---|
| 244 | .release		= single_release, | 
|---|
| 245 | }; | 
|---|
| 246 |  | 
|---|
| 247 | static int xhci_slot_context_show(struct seq_file *s, void *unused) | 
|---|
| 248 | { | 
|---|
| 249 | struct xhci_hcd		*xhci; | 
|---|
| 250 | struct xhci_slot_ctx	*slot_ctx; | 
|---|
| 251 | struct xhci_slot_priv	*priv = s->private; | 
|---|
| 252 | struct xhci_virt_device	*dev = priv->dev; | 
|---|
| 253 | char			str[XHCI_MSG_MAX]; | 
|---|
| 254 |  | 
|---|
| 255 | xhci = hcd_to_xhci(hcd: bus_to_hcd(bus: dev->udev->bus)); | 
|---|
| 256 | slot_ctx = xhci_get_slot_ctx(xhci, ctx: dev->out_ctx); | 
|---|
| 257 | seq_printf(m: s, fmt: "%pad: %s\n", &dev->out_ctx->dma, | 
|---|
| 258 | xhci_decode_slot_context(str, | 
|---|
| 259 | le32_to_cpu(slot_ctx->dev_info), | 
|---|
| 260 | le32_to_cpu(slot_ctx->dev_info2), | 
|---|
| 261 | le32_to_cpu(slot_ctx->tt_info), | 
|---|
| 262 | le32_to_cpu(slot_ctx->dev_state))); | 
|---|
| 263 |  | 
|---|
| 264 | return 0; | 
|---|
| 265 | } | 
|---|
| 266 |  | 
|---|
| 267 | static int xhci_endpoint_context_show(struct seq_file *s, void *unused) | 
|---|
| 268 | { | 
|---|
| 269 | int			ep_index; | 
|---|
| 270 | dma_addr_t		dma; | 
|---|
| 271 | struct xhci_hcd		*xhci; | 
|---|
| 272 | struct xhci_ep_ctx	*ep_ctx; | 
|---|
| 273 | struct xhci_slot_priv	*priv = s->private; | 
|---|
| 274 | struct xhci_virt_device	*dev = priv->dev; | 
|---|
| 275 | char			str[XHCI_MSG_MAX]; | 
|---|
| 276 |  | 
|---|
| 277 | xhci = hcd_to_xhci(hcd: bus_to_hcd(bus: dev->udev->bus)); | 
|---|
| 278 |  | 
|---|
| 279 | for (ep_index = 0; ep_index < 31; ep_index++) { | 
|---|
| 280 | ep_ctx = xhci_get_ep_ctx(xhci, ctx: dev->out_ctx, ep_index); | 
|---|
| 281 | dma = dev->out_ctx->dma + (ep_index + 1) * CTX_SIZE(xhci->hcc_params); | 
|---|
| 282 | seq_printf(m: s, fmt: "%pad: %s, virt_state:%#x\n", &dma, | 
|---|
| 283 | xhci_decode_ep_context(str, | 
|---|
| 284 | le32_to_cpu(ep_ctx->ep_info), | 
|---|
| 285 | le32_to_cpu(ep_ctx->ep_info2), | 
|---|
| 286 | le64_to_cpu(ep_ctx->deq), | 
|---|
| 287 | le32_to_cpu(ep_ctx->tx_info)), | 
|---|
| 288 | dev->eps[ep_index].ep_state); | 
|---|
| 289 | } | 
|---|
| 290 |  | 
|---|
| 291 | return 0; | 
|---|
| 292 | } | 
|---|
| 293 |  | 
|---|
| 294 | static int xhci_device_name_show(struct seq_file *s, void *unused) | 
|---|
| 295 | { | 
|---|
| 296 | struct xhci_slot_priv	*priv = s->private; | 
|---|
| 297 | struct xhci_virt_device	*dev = priv->dev; | 
|---|
| 298 |  | 
|---|
| 299 | seq_printf(m: s, fmt: "%s\n", dev_name(dev: &dev->udev->dev)); | 
|---|
| 300 |  | 
|---|
| 301 | return 0; | 
|---|
| 302 | } | 
|---|
| 303 |  | 
|---|
| 304 | static struct xhci_file_map context_files[] = { | 
|---|
| 305 | { "name",		xhci_device_name_show, }, | 
|---|
| 306 | { "slot-context",	xhci_slot_context_show, }, | 
|---|
| 307 | { "ep-context",		xhci_endpoint_context_show, }, | 
|---|
| 308 | }; | 
|---|
| 309 |  | 
|---|
| 310 | static int xhci_context_open(struct inode *inode, struct file *file) | 
|---|
| 311 | { | 
|---|
| 312 | const struct xhci_file_map *f_map = debugfs_get_aux(file); | 
|---|
| 313 |  | 
|---|
| 314 | return single_open(file, f_map->show, inode->i_private); | 
|---|
| 315 | } | 
|---|
| 316 |  | 
|---|
| 317 | static const struct file_operations xhci_context_fops = { | 
|---|
| 318 | .open			= xhci_context_open, | 
|---|
| 319 | .read			= seq_read, | 
|---|
| 320 | .llseek			= seq_lseek, | 
|---|
| 321 | .release		= single_release, | 
|---|
| 322 | }; | 
|---|
| 323 |  | 
|---|
| 324 |  | 
|---|
| 325 |  | 
|---|
| 326 | static int xhci_portsc_show(struct seq_file *s, void *unused) | 
|---|
| 327 | { | 
|---|
| 328 | struct xhci_port	*port = s->private; | 
|---|
| 329 | u32			portsc; | 
|---|
| 330 | char			str[XHCI_MSG_MAX]; | 
|---|
| 331 |  | 
|---|
| 332 | portsc = readl(addr: port->addr); | 
|---|
| 333 | seq_printf(m: s, fmt: "%s\n", xhci_decode_portsc(str, portsc)); | 
|---|
| 334 |  | 
|---|
| 335 | return 0; | 
|---|
| 336 | } | 
|---|
| 337 |  | 
|---|
| 338 | static int xhci_port_open(struct inode *inode, struct file *file) | 
|---|
| 339 | { | 
|---|
| 340 | return single_open(file, xhci_portsc_show, inode->i_private); | 
|---|
| 341 | } | 
|---|
| 342 |  | 
|---|
| 343 | static ssize_t xhci_port_write(struct file *file,  const char __user *ubuf, | 
|---|
| 344 | size_t count, loff_t *ppos) | 
|---|
| 345 | { | 
|---|
| 346 | struct seq_file         *s = file->private_data; | 
|---|
| 347 | struct xhci_port	*port = s->private; | 
|---|
| 348 | struct xhci_hcd		*xhci = hcd_to_xhci(hcd: port->rhub->hcd); | 
|---|
| 349 | char                    buf[32]; | 
|---|
| 350 | u32			portsc; | 
|---|
| 351 | unsigned long		flags; | 
|---|
| 352 |  | 
|---|
| 353 | if (copy_from_user(to: &buf, from: ubuf, min_t(size_t, sizeof(buf) - 1, count))) | 
|---|
| 354 | return -EFAULT; | 
|---|
| 355 |  | 
|---|
| 356 | if (!strncmp(buf, "compliance", 10)) { | 
|---|
| 357 | /* If CTC is clear, compliance is enabled by default */ | 
|---|
| 358 | if (!HCC2_CTC(xhci->hcc_params2)) | 
|---|
| 359 | return count; | 
|---|
| 360 | spin_lock_irqsave(&xhci->lock, flags); | 
|---|
| 361 | /* compliance mode can only be enabled on ports in RxDetect */ | 
|---|
| 362 | portsc = readl(addr: port->addr); | 
|---|
| 363 | if ((portsc & PORT_PLS_MASK) != XDEV_RXDETECT) { | 
|---|
| 364 | spin_unlock_irqrestore(lock: &xhci->lock, flags); | 
|---|
| 365 | return -EPERM; | 
|---|
| 366 | } | 
|---|
| 367 | portsc = xhci_port_state_to_neutral(state: portsc); | 
|---|
| 368 | portsc &= ~PORT_PLS_MASK; | 
|---|
| 369 | portsc |= PORT_LINK_STROBE | XDEV_COMP_MODE; | 
|---|
| 370 | writel(val: portsc, addr: port->addr); | 
|---|
| 371 | spin_unlock_irqrestore(lock: &xhci->lock, flags); | 
|---|
| 372 | } else { | 
|---|
| 373 | return -EINVAL; | 
|---|
| 374 | } | 
|---|
| 375 | return count; | 
|---|
| 376 | } | 
|---|
| 377 |  | 
|---|
| 378 | static const struct file_operations port_fops = { | 
|---|
| 379 | .open			= xhci_port_open, | 
|---|
| 380 | .write                  = xhci_port_write, | 
|---|
| 381 | .read			= seq_read, | 
|---|
| 382 | .llseek			= seq_lseek, | 
|---|
| 383 | .release		= single_release, | 
|---|
| 384 | }; | 
|---|
| 385 |  | 
|---|
| 386 | static void xhci_debugfs_create_files(struct xhci_hcd *xhci, | 
|---|
| 387 | struct xhci_file_map *files, | 
|---|
| 388 | size_t nentries, void *data, | 
|---|
| 389 | struct dentry *parent, | 
|---|
| 390 | const struct file_operations *fops) | 
|---|
| 391 | { | 
|---|
| 392 | int			i; | 
|---|
| 393 |  | 
|---|
| 394 | for (i = 0; i < nentries; i++) | 
|---|
| 395 | debugfs_create_file_aux(files[i].name, 0444, parent, | 
|---|
| 396 | data, &files[i], fops); | 
|---|
| 397 | } | 
|---|
| 398 |  | 
|---|
| 399 | static struct dentry *xhci_debugfs_create_ring_dir(struct xhci_hcd *xhci, | 
|---|
| 400 | struct xhci_ring **ring, | 
|---|
| 401 | const char *name, | 
|---|
| 402 | struct dentry *parent) | 
|---|
| 403 | { | 
|---|
| 404 | struct dentry		*dir; | 
|---|
| 405 |  | 
|---|
| 406 | dir = debugfs_create_dir(name, parent); | 
|---|
| 407 | xhci_debugfs_create_files(xhci, files: ring_files, ARRAY_SIZE(ring_files), | 
|---|
| 408 | data: ring, parent: dir, fops: &xhci_ring_fops); | 
|---|
| 409 |  | 
|---|
| 410 | return dir; | 
|---|
| 411 | } | 
|---|
| 412 |  | 
|---|
| 413 | static void xhci_debugfs_create_context_files(struct xhci_hcd *xhci, | 
|---|
| 414 | struct dentry *parent, | 
|---|
| 415 | int slot_id) | 
|---|
| 416 | { | 
|---|
| 417 | struct xhci_virt_device	*dev = xhci->devs[slot_id]; | 
|---|
| 418 |  | 
|---|
| 419 | xhci_debugfs_create_files(xhci, files: context_files, | 
|---|
| 420 | ARRAY_SIZE(context_files), | 
|---|
| 421 | data: dev->debugfs_private, | 
|---|
| 422 | parent, fops: &xhci_context_fops); | 
|---|
| 423 | } | 
|---|
| 424 |  | 
|---|
| 425 | void xhci_debugfs_create_endpoint(struct xhci_hcd *xhci, | 
|---|
| 426 | struct xhci_virt_device *dev, | 
|---|
| 427 | int ep_index) | 
|---|
| 428 | { | 
|---|
| 429 | struct xhci_ep_priv	*epriv; | 
|---|
| 430 | struct xhci_slot_priv	*spriv = dev->debugfs_private; | 
|---|
| 431 |  | 
|---|
| 432 | if (!spriv) | 
|---|
| 433 | return; | 
|---|
| 434 |  | 
|---|
| 435 | if (spriv->eps[ep_index]) | 
|---|
| 436 | return; | 
|---|
| 437 |  | 
|---|
| 438 | epriv = kzalloc(sizeof(*epriv), GFP_KERNEL); | 
|---|
| 439 | if (!epriv) | 
|---|
| 440 | return; | 
|---|
| 441 |  | 
|---|
| 442 | epriv->show_ring = dev->eps[ep_index].ring; | 
|---|
| 443 |  | 
|---|
| 444 | snprintf(buf: epriv->name, size: sizeof(epriv->name), fmt: "ep%02d", ep_index); | 
|---|
| 445 | epriv->root = xhci_debugfs_create_ring_dir(xhci, | 
|---|
| 446 | ring: &epriv->show_ring, | 
|---|
| 447 | name: epriv->name, | 
|---|
| 448 | parent: spriv->root); | 
|---|
| 449 | spriv->eps[ep_index] = epriv; | 
|---|
| 450 | } | 
|---|
| 451 |  | 
|---|
| 452 | void xhci_debugfs_remove_endpoint(struct xhci_hcd *xhci, | 
|---|
| 453 | struct xhci_virt_device *dev, | 
|---|
| 454 | int ep_index) | 
|---|
| 455 | { | 
|---|
| 456 | struct xhci_ep_priv	*epriv; | 
|---|
| 457 | struct xhci_slot_priv	*spriv = dev->debugfs_private; | 
|---|
| 458 |  | 
|---|
| 459 | if (!spriv || !spriv->eps[ep_index]) | 
|---|
| 460 | return; | 
|---|
| 461 |  | 
|---|
| 462 | epriv = spriv->eps[ep_index]; | 
|---|
| 463 | debugfs_remove_recursive(dentry: epriv->root); | 
|---|
| 464 | spriv->eps[ep_index] = NULL; | 
|---|
| 465 | kfree(objp: epriv); | 
|---|
| 466 | } | 
|---|
| 467 |  | 
|---|
| 468 | static int xhci_stream_id_show(struct seq_file *s, void *unused) | 
|---|
| 469 | { | 
|---|
| 470 | struct xhci_ep_priv	*epriv = s->private; | 
|---|
| 471 |  | 
|---|
| 472 | if (!epriv->stream_info) | 
|---|
| 473 | return -EPERM; | 
|---|
| 474 |  | 
|---|
| 475 | seq_printf(m: s, fmt: "Show stream ID %d trb ring, supported [1 - %d]\n", | 
|---|
| 476 | epriv->stream_id, epriv->stream_info->num_streams - 1); | 
|---|
| 477 |  | 
|---|
| 478 | return 0; | 
|---|
| 479 | } | 
|---|
| 480 |  | 
|---|
| 481 | static int xhci_stream_id_open(struct inode *inode, struct file *file) | 
|---|
| 482 | { | 
|---|
| 483 | return single_open(file, xhci_stream_id_show, inode->i_private); | 
|---|
| 484 | } | 
|---|
| 485 |  | 
|---|
| 486 | static ssize_t xhci_stream_id_write(struct file *file,  const char __user *ubuf, | 
|---|
| 487 | size_t count, loff_t *ppos) | 
|---|
| 488 | { | 
|---|
| 489 | struct seq_file         *s = file->private_data; | 
|---|
| 490 | struct xhci_ep_priv	*epriv = s->private; | 
|---|
| 491 | int			ret; | 
|---|
| 492 | u16			stream_id; /* MaxPStreams + 1 <= 16 */ | 
|---|
| 493 |  | 
|---|
| 494 | if (!epriv->stream_info) | 
|---|
| 495 | return -EPERM; | 
|---|
| 496 |  | 
|---|
| 497 | /* Decimal number */ | 
|---|
| 498 | ret = kstrtou16_from_user(s: ubuf, count, base: 10, res: &stream_id); | 
|---|
| 499 | if (ret) | 
|---|
| 500 | return ret; | 
|---|
| 501 |  | 
|---|
| 502 | if (stream_id == 0 || stream_id >= epriv->stream_info->num_streams) | 
|---|
| 503 | return -EINVAL; | 
|---|
| 504 |  | 
|---|
| 505 | epriv->stream_id = stream_id; | 
|---|
| 506 | epriv->show_ring = epriv->stream_info->stream_rings[stream_id]; | 
|---|
| 507 |  | 
|---|
| 508 | return count; | 
|---|
| 509 | } | 
|---|
| 510 |  | 
|---|
| 511 | static const struct file_operations stream_id_fops = { | 
|---|
| 512 | .open			= xhci_stream_id_open, | 
|---|
| 513 | .write                  = xhci_stream_id_write, | 
|---|
| 514 | .read			= seq_read, | 
|---|
| 515 | .llseek			= seq_lseek, | 
|---|
| 516 | .release		= single_release, | 
|---|
| 517 | }; | 
|---|
| 518 |  | 
|---|
| 519 | static int xhci_stream_context_array_show(struct seq_file *s, void *unused) | 
|---|
| 520 | { | 
|---|
| 521 | struct xhci_ep_priv	*epriv = s->private; | 
|---|
| 522 | struct xhci_stream_ctx	*stream_ctx; | 
|---|
| 523 | dma_addr_t		dma; | 
|---|
| 524 | int			id; | 
|---|
| 525 |  | 
|---|
| 526 | if (!epriv->stream_info) | 
|---|
| 527 | return -EPERM; | 
|---|
| 528 |  | 
|---|
| 529 | seq_printf(m: s, fmt: "Allocated %d streams and %d stream context array entries\n", | 
|---|
| 530 | epriv->stream_info->num_streams, | 
|---|
| 531 | epriv->stream_info->num_stream_ctxs); | 
|---|
| 532 |  | 
|---|
| 533 | for (id = 0; id < epriv->stream_info->num_stream_ctxs; id++) { | 
|---|
| 534 | stream_ctx = epriv->stream_info->stream_ctx_array + id; | 
|---|
| 535 | dma = epriv->stream_info->ctx_array_dma + id * 16; | 
|---|
| 536 | if (id < epriv->stream_info->num_streams) | 
|---|
| 537 | seq_printf(m: s, fmt: "%pad stream id %d deq %016llx\n", &dma, | 
|---|
| 538 | id, le64_to_cpu(stream_ctx->stream_ring)); | 
|---|
| 539 | else | 
|---|
| 540 | seq_printf(m: s, fmt: "%pad stream context entry not used deq %016llx\n", | 
|---|
| 541 | &dma, le64_to_cpu(stream_ctx->stream_ring)); | 
|---|
| 542 | } | 
|---|
| 543 |  | 
|---|
| 544 | return 0; | 
|---|
| 545 | } | 
|---|
| 546 | DEFINE_SHOW_ATTRIBUTE(xhci_stream_context_array); | 
|---|
| 547 |  | 
|---|
| 548 | void xhci_debugfs_create_stream_files(struct xhci_hcd *xhci, | 
|---|
| 549 | struct xhci_virt_device *dev, | 
|---|
| 550 | int ep_index) | 
|---|
| 551 | { | 
|---|
| 552 | struct xhci_slot_priv	*spriv = dev->debugfs_private; | 
|---|
| 553 | struct xhci_ep_priv	*epriv; | 
|---|
| 554 |  | 
|---|
| 555 | if (!spriv || !spriv->eps[ep_index] || | 
|---|
| 556 | !dev->eps[ep_index].stream_info) | 
|---|
| 557 | return; | 
|---|
| 558 |  | 
|---|
| 559 | epriv = spriv->eps[ep_index]; | 
|---|
| 560 | epriv->stream_info = dev->eps[ep_index].stream_info; | 
|---|
| 561 |  | 
|---|
| 562 | /* Show trb ring of stream ID 1 by default */ | 
|---|
| 563 | epriv->stream_id = 1; | 
|---|
| 564 | epriv->show_ring = epriv->stream_info->stream_rings[1]; | 
|---|
| 565 | debugfs_create_file( "stream_id", 0644, | 
|---|
| 566 | epriv->root, epriv, | 
|---|
| 567 | &stream_id_fops); | 
|---|
| 568 | debugfs_create_file( "stream_context_array", 0444, | 
|---|
| 569 | epriv->root, epriv, | 
|---|
| 570 | &xhci_stream_context_array_fops); | 
|---|
| 571 | } | 
|---|
| 572 |  | 
|---|
| 573 | void xhci_debugfs_create_slot(struct xhci_hcd *xhci, int slot_id) | 
|---|
| 574 | { | 
|---|
| 575 | struct xhci_slot_priv	*priv; | 
|---|
| 576 | struct xhci_virt_device	*dev = xhci->devs[slot_id]; | 
|---|
| 577 |  | 
|---|
| 578 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | 
|---|
| 579 | if (!priv) | 
|---|
| 580 | return; | 
|---|
| 581 |  | 
|---|
| 582 | snprintf(buf: priv->name, size: sizeof(priv->name), fmt: "%02d", slot_id); | 
|---|
| 583 | priv->root = debugfs_create_dir(name: priv->name, parent: xhci->debugfs_slots); | 
|---|
| 584 | priv->dev = dev; | 
|---|
| 585 | dev->debugfs_private = priv; | 
|---|
| 586 |  | 
|---|
| 587 | xhci_debugfs_create_ring_dir(xhci, ring: &dev->eps[0].ring, | 
|---|
| 588 | name: "ep00", parent: priv->root); | 
|---|
| 589 |  | 
|---|
| 590 | xhci_debugfs_create_context_files(xhci, parent: priv->root, slot_id); | 
|---|
| 591 | } | 
|---|
| 592 |  | 
|---|
| 593 | void xhci_debugfs_remove_slot(struct xhci_hcd *xhci, int slot_id) | 
|---|
| 594 | { | 
|---|
| 595 | int			i; | 
|---|
| 596 | struct xhci_slot_priv	*priv; | 
|---|
| 597 | struct xhci_virt_device	*dev = xhci->devs[slot_id]; | 
|---|
| 598 |  | 
|---|
| 599 | if (!dev || !dev->debugfs_private) | 
|---|
| 600 | return; | 
|---|
| 601 |  | 
|---|
| 602 | priv = dev->debugfs_private; | 
|---|
| 603 |  | 
|---|
| 604 | debugfs_remove_recursive(dentry: priv->root); | 
|---|
| 605 |  | 
|---|
| 606 | for (i = 0; i < 31; i++) | 
|---|
| 607 | kfree(objp: priv->eps[i]); | 
|---|
| 608 |  | 
|---|
| 609 | kfree(objp: priv); | 
|---|
| 610 | dev->debugfs_private = NULL; | 
|---|
| 611 | } | 
|---|
| 612 |  | 
|---|
| 613 | static void xhci_debugfs_create_ports(struct xhci_hcd *xhci, | 
|---|
| 614 | struct dentry *parent) | 
|---|
| 615 | { | 
|---|
| 616 | unsigned int		num_ports; | 
|---|
| 617 | char			port_name[8]; | 
|---|
| 618 | struct xhci_port	*port; | 
|---|
| 619 | struct dentry		*dir; | 
|---|
| 620 |  | 
|---|
| 621 | num_ports = HCS_MAX_PORTS(xhci->hcs_params1); | 
|---|
| 622 |  | 
|---|
| 623 | parent = debugfs_create_dir(name: "ports", parent); | 
|---|
| 624 |  | 
|---|
| 625 | while (num_ports--) { | 
|---|
| 626 | scnprintf(buf: port_name, size: sizeof(port_name), fmt: "port%02d", | 
|---|
| 627 | num_ports + 1); | 
|---|
| 628 | dir = debugfs_create_dir(name: port_name, parent); | 
|---|
| 629 | port = &xhci->hw_ports[num_ports]; | 
|---|
| 630 | debugfs_create_file( "portsc", 0644, dir, port, &port_fops); | 
|---|
| 631 | } | 
|---|
| 632 | } | 
|---|
| 633 |  | 
|---|
| 634 | static int xhci_port_bw_show(struct xhci_hcd *xhci, u8 dev_speed, | 
|---|
| 635 | struct seq_file *s) | 
|---|
| 636 | { | 
|---|
| 637 | unsigned int			num_ports; | 
|---|
| 638 | unsigned int			i; | 
|---|
| 639 | int				ret; | 
|---|
| 640 | struct xhci_container_ctx	*ctx; | 
|---|
| 641 | struct usb_hcd			*hcd = xhci_to_hcd(xhci); | 
|---|
| 642 | struct device			*dev = hcd->self.controller; | 
|---|
| 643 |  | 
|---|
| 644 | ret = pm_runtime_get_sync(dev); | 
|---|
| 645 | if (ret < 0) | 
|---|
| 646 | return ret; | 
|---|
| 647 |  | 
|---|
| 648 | num_ports = HCS_MAX_PORTS(xhci->hcs_params1); | 
|---|
| 649 |  | 
|---|
| 650 | ctx = xhci_alloc_port_bw_ctx(xhci, flags: 0); | 
|---|
| 651 | if (!ctx) { | 
|---|
| 652 | pm_runtime_put_sync(dev); | 
|---|
| 653 | return -ENOMEM; | 
|---|
| 654 | } | 
|---|
| 655 |  | 
|---|
| 656 | /* get roothub port bandwidth */ | 
|---|
| 657 | ret = xhci_get_port_bandwidth(xhci, ctx, dev_speed); | 
|---|
| 658 | if (ret) | 
|---|
| 659 | goto err_out; | 
|---|
| 660 |  | 
|---|
| 661 | /* print all roothub ports available bandwidth | 
|---|
| 662 | * refer to xhci rev1_2 protocol 6.2.6 , byte 0 is reserved | 
|---|
| 663 | */ | 
|---|
| 664 | for (i = 1; i < num_ports+1; i++) | 
|---|
| 665 | seq_printf(m: s, fmt: "port[%d] available bw: %d%%.\n", i, | 
|---|
| 666 | ctx->bytes[i]); | 
|---|
| 667 | err_out: | 
|---|
| 668 | pm_runtime_put_sync(dev); | 
|---|
| 669 | xhci_free_port_bw_ctx(xhci, ctx); | 
|---|
| 670 | return ret; | 
|---|
| 671 | } | 
|---|
| 672 |  | 
|---|
| 673 | static int xhci_ss_bw_show(struct seq_file *s, void *unused) | 
|---|
| 674 | { | 
|---|
| 675 | int ret; | 
|---|
| 676 | struct xhci_hcd		*xhci = (struct xhci_hcd *)s->private; | 
|---|
| 677 |  | 
|---|
| 678 | ret = xhci_port_bw_show(xhci, dev_speed: USB_SPEED_SUPER, s); | 
|---|
| 679 | return ret; | 
|---|
| 680 | } | 
|---|
| 681 |  | 
|---|
| 682 | static int xhci_hs_bw_show(struct seq_file *s, void *unused) | 
|---|
| 683 | { | 
|---|
| 684 | int ret; | 
|---|
| 685 | struct xhci_hcd		*xhci = (struct xhci_hcd *)s->private; | 
|---|
| 686 |  | 
|---|
| 687 | ret = xhci_port_bw_show(xhci, dev_speed: USB_SPEED_HIGH, s); | 
|---|
| 688 | return ret; | 
|---|
| 689 | } | 
|---|
| 690 |  | 
|---|
| 691 | static int xhci_fs_bw_show(struct seq_file *s, void *unused) | 
|---|
| 692 | { | 
|---|
| 693 | int ret; | 
|---|
| 694 | struct xhci_hcd		*xhci = (struct xhci_hcd *)s->private; | 
|---|
| 695 |  | 
|---|
| 696 | ret = xhci_port_bw_show(xhci, dev_speed: USB_SPEED_FULL, s); | 
|---|
| 697 | return ret; | 
|---|
| 698 | } | 
|---|
| 699 |  | 
|---|
| 700 | static struct xhci_file_map bw_context_files[] = { | 
|---|
| 701 | { "SS_BW",	xhci_ss_bw_show, }, | 
|---|
| 702 | { "HS_BW",	xhci_hs_bw_show, }, | 
|---|
| 703 | { "FS_BW",	xhci_fs_bw_show, }, | 
|---|
| 704 | }; | 
|---|
| 705 |  | 
|---|
| 706 | static int bw_context_open(struct inode *inode, struct file *file) | 
|---|
| 707 | { | 
|---|
| 708 | int			i; | 
|---|
| 709 | struct xhci_file_map	*f_map; | 
|---|
| 710 | const char		*file_name = file_dentry(file)->d_iname; | 
|---|
| 711 |  | 
|---|
| 712 | for (i = 0; i < ARRAY_SIZE(bw_context_files); i++) { | 
|---|
| 713 | f_map = &bw_context_files[i]; | 
|---|
| 714 |  | 
|---|
| 715 | if (strcmp(f_map->name, file_name) == 0) | 
|---|
| 716 | break; | 
|---|
| 717 | } | 
|---|
| 718 |  | 
|---|
| 719 | return single_open(file, f_map->show, inode->i_private); | 
|---|
| 720 | } | 
|---|
| 721 |  | 
|---|
| 722 | static const struct file_operations bw_fops = { | 
|---|
| 723 | .open			= bw_context_open, | 
|---|
| 724 | .read			= seq_read, | 
|---|
| 725 | .llseek			= seq_lseek, | 
|---|
| 726 | .release		= single_release, | 
|---|
| 727 | }; | 
|---|
| 728 |  | 
|---|
| 729 | static void xhci_debugfs_create_bandwidth(struct xhci_hcd *xhci, | 
|---|
| 730 | struct dentry *parent) | 
|---|
| 731 | { | 
|---|
| 732 | parent = debugfs_create_dir(name: "port_bandwidth", parent); | 
|---|
| 733 |  | 
|---|
| 734 | xhci_debugfs_create_files(xhci, files: bw_context_files, | 
|---|
| 735 | ARRAY_SIZE(bw_context_files), | 
|---|
| 736 | data: xhci, | 
|---|
| 737 | parent, fops: &bw_fops); | 
|---|
| 738 | } | 
|---|
| 739 |  | 
|---|
| 740 | void xhci_debugfs_init(struct xhci_hcd *xhci) | 
|---|
| 741 | { | 
|---|
| 742 | struct device		*dev = xhci_to_hcd(xhci)->self.controller; | 
|---|
| 743 |  | 
|---|
| 744 | xhci->debugfs_root = debugfs_create_dir(name: dev_name(dev), | 
|---|
| 745 | parent: xhci_debugfs_root); | 
|---|
| 746 |  | 
|---|
| 747 | INIT_LIST_HEAD(list: &xhci->regset_list); | 
|---|
| 748 |  | 
|---|
| 749 | xhci_debugfs_regset(xhci, | 
|---|
| 750 | base: 0, | 
|---|
| 751 | regs: xhci_cap_regs, ARRAY_SIZE(xhci_cap_regs), | 
|---|
| 752 | parent: xhci->debugfs_root, fmt: "reg-cap"); | 
|---|
| 753 |  | 
|---|
| 754 | xhci_debugfs_regset(xhci, | 
|---|
| 755 | HC_LENGTH(readl(&xhci->cap_regs->hc_capbase)), | 
|---|
| 756 | regs: xhci_op_regs, ARRAY_SIZE(xhci_op_regs), | 
|---|
| 757 | parent: xhci->debugfs_root, fmt: "reg-op"); | 
|---|
| 758 |  | 
|---|
| 759 | xhci_debugfs_regset(xhci, | 
|---|
| 760 | readl(addr: &xhci->cap_regs->run_regs_off) & RTSOFF_MASK, | 
|---|
| 761 | regs: xhci_runtime_regs, ARRAY_SIZE(xhci_runtime_regs), | 
|---|
| 762 | parent: xhci->debugfs_root, fmt: "reg-runtime"); | 
|---|
| 763 |  | 
|---|
| 764 | xhci_debugfs_extcap_regset(xhci, XHCI_EXT_CAPS_LEGACY, | 
|---|
| 765 | regs: xhci_extcap_legsup, | 
|---|
| 766 | ARRAY_SIZE(xhci_extcap_legsup), | 
|---|
| 767 | cap_name: "reg-ext-legsup"); | 
|---|
| 768 |  | 
|---|
| 769 | xhci_debugfs_extcap_regset(xhci, XHCI_EXT_CAPS_PROTOCOL, | 
|---|
| 770 | regs: xhci_extcap_protocol, | 
|---|
| 771 | ARRAY_SIZE(xhci_extcap_protocol), | 
|---|
| 772 | cap_name: "reg-ext-protocol"); | 
|---|
| 773 |  | 
|---|
| 774 | xhci_debugfs_extcap_regset(xhci, XHCI_EXT_CAPS_DEBUG, | 
|---|
| 775 | regs: xhci_extcap_dbc, | 
|---|
| 776 | ARRAY_SIZE(xhci_extcap_dbc), | 
|---|
| 777 | cap_name: "reg-ext-dbc"); | 
|---|
| 778 |  | 
|---|
| 779 | xhci_debugfs_create_ring_dir(xhci, ring: &xhci->cmd_ring, | 
|---|
| 780 | name: "command-ring", | 
|---|
| 781 | parent: xhci->debugfs_root); | 
|---|
| 782 |  | 
|---|
| 783 | xhci_debugfs_create_ring_dir(xhci, ring: &xhci->interrupters[0]->event_ring, | 
|---|
| 784 | name: "event-ring", | 
|---|
| 785 | parent: xhci->debugfs_root); | 
|---|
| 786 |  | 
|---|
| 787 | xhci->debugfs_slots = debugfs_create_dir(name: "devices", parent: xhci->debugfs_root); | 
|---|
| 788 |  | 
|---|
| 789 | xhci_debugfs_create_ports(xhci, parent: xhci->debugfs_root); | 
|---|
| 790 |  | 
|---|
| 791 | xhci_debugfs_create_bandwidth(xhci, parent: xhci->debugfs_root); | 
|---|
| 792 | } | 
|---|
| 793 |  | 
|---|
| 794 | void xhci_debugfs_exit(struct xhci_hcd *xhci) | 
|---|
| 795 | { | 
|---|
| 796 | struct xhci_regset	*rgs, *tmp; | 
|---|
| 797 |  | 
|---|
| 798 | debugfs_remove_recursive(dentry: xhci->debugfs_root); | 
|---|
| 799 | xhci->debugfs_root = NULL; | 
|---|
| 800 | xhci->debugfs_slots = NULL; | 
|---|
| 801 |  | 
|---|
| 802 | list_for_each_entry_safe(rgs, tmp, &xhci->regset_list, list) | 
|---|
| 803 | xhci_debugfs_free_regset(regset: rgs); | 
|---|
| 804 | } | 
|---|
| 805 |  | 
|---|
| 806 | void __init xhci_debugfs_create_root(void) | 
|---|
| 807 | { | 
|---|
| 808 | xhci_debugfs_root = debugfs_create_dir(name: "xhci", parent: usb_debug_root); | 
|---|
| 809 | } | 
|---|
| 810 |  | 
|---|
| 811 | void __exit xhci_debugfs_remove_root(void) | 
|---|
| 812 | { | 
|---|
| 813 | debugfs_remove_recursive(dentry: xhci_debugfs_root); | 
|---|
| 814 | xhci_debugfs_root = NULL; | 
|---|
| 815 | } | 
|---|
| 816 |  | 
|---|