123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011 |
- // SPDX-License-Identifier: GPL-2.0
- /**
- * xhci-dbc.c - xHCI debug capability early driver
- *
- * Copyright (C) 2016 Intel Corporation
- *
- * Author: Lu Baolu <baolu.lu@linux.intel.com>
- */
- #define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
- #include <linux/console.h>
- #include <linux/pci_regs.h>
- #include <linux/pci_ids.h>
- #include <linux/bootmem.h>
- #include <linux/io.h>
- #include <asm/pci-direct.h>
- #include <asm/fixmap.h>
- #include <linux/bcd.h>
- #include <linux/export.h>
- #include <linux/version.h>
- #include <linux/module.h>
- #include <linux/delay.h>
- #include <linux/kthread.h>
- #include "../host/xhci.h"
- #include "xhci-dbc.h"
- static struct xdbc_state xdbc;
- static bool early_console_keep;
- #ifdef XDBC_TRACE
- #define xdbc_trace trace_printk
- #else
- static inline void xdbc_trace(const char *fmt, ...) { }
- #endif /* XDBC_TRACE */
- static void __iomem * __init xdbc_map_pci_mmio(u32 bus, u32 dev, u32 func)
- {
- u64 val64, sz64, mask64;
- void __iomem *base;
- u32 val, sz;
- u8 byte;
- val = read_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0);
- write_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0, ~0);
- sz = read_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0);
- write_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0, val);
- if (val == 0xffffffff || sz == 0xffffffff) {
- pr_notice("invalid mmio bar\n");
- return NULL;
- }
- val64 = val & PCI_BASE_ADDRESS_MEM_MASK;
- sz64 = sz & PCI_BASE_ADDRESS_MEM_MASK;
- mask64 = PCI_BASE_ADDRESS_MEM_MASK;
- if ((val & PCI_BASE_ADDRESS_MEM_TYPE_MASK) == PCI_BASE_ADDRESS_MEM_TYPE_64) {
- val = read_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0 + 4);
- write_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0 + 4, ~0);
- sz = read_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0 + 4);
- write_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0 + 4, val);
- val64 |= (u64)val << 32;
- sz64 |= (u64)sz << 32;
- mask64 |= ~0ULL << 32;
- }
- sz64 &= mask64;
- if (!sz64) {
- pr_notice("invalid mmio address\n");
- return NULL;
- }
- sz64 = 1ULL << __ffs64(sz64);
- /* Check if the mem space is enabled: */
- byte = read_pci_config_byte(bus, dev, func, PCI_COMMAND);
- if (!(byte & PCI_COMMAND_MEMORY)) {
- byte |= PCI_COMMAND_MEMORY;
- write_pci_config_byte(bus, dev, func, PCI_COMMAND, byte);
- }
- xdbc.xhci_start = val64;
- xdbc.xhci_length = sz64;
- base = early_ioremap(val64, sz64);
- return base;
- }
- static void * __init xdbc_get_page(dma_addr_t *dma_addr)
- {
- void *virt;
- virt = alloc_bootmem_pages_nopanic(PAGE_SIZE);
- if (!virt)
- return NULL;
- if (dma_addr)
- *dma_addr = (dma_addr_t)__pa(virt);
- return virt;
- }
- static u32 __init xdbc_find_dbgp(int xdbc_num, u32 *b, u32 *d, u32 *f)
- {
- u32 bus, dev, func, class;
- for (bus = 0; bus < XDBC_PCI_MAX_BUSES; bus++) {
- for (dev = 0; dev < XDBC_PCI_MAX_DEVICES; dev++) {
- for (func = 0; func < XDBC_PCI_MAX_FUNCTION; func++) {
- class = read_pci_config(bus, dev, func, PCI_CLASS_REVISION);
- if ((class >> 8) != PCI_CLASS_SERIAL_USB_XHCI)
- continue;
- if (xdbc_num-- != 0)
- continue;
- *b = bus;
- *d = dev;
- *f = func;
- return 0;
- }
- }
- }
- return -1;
- }
- static int handshake(void __iomem *ptr, u32 mask, u32 done, int wait, int delay)
- {
- u32 result;
- do {
- result = readl(ptr);
- result &= mask;
- if (result == done)
- return 0;
- udelay(delay);
- wait -= delay;
- } while (wait > 0);
- return -ETIMEDOUT;
- }
- static void __init xdbc_bios_handoff(void)
- {
- int offset, timeout;
- u32 val;
- offset = xhci_find_next_ext_cap(xdbc.xhci_base, 0, XHCI_EXT_CAPS_LEGACY);
- val = readl(xdbc.xhci_base + offset);
- if (val & XHCI_HC_BIOS_OWNED) {
- writel(val | XHCI_HC_OS_OWNED, xdbc.xhci_base + offset);
- timeout = handshake(xdbc.xhci_base + offset, XHCI_HC_BIOS_OWNED, 0, 5000, 10);
- if (timeout) {
- pr_notice("failed to hand over xHCI control from BIOS\n");
- writel(val & ~XHCI_HC_BIOS_OWNED, xdbc.xhci_base + offset);
- }
- }
- /* Disable BIOS SMIs and clear all SMI events: */
- val = readl(xdbc.xhci_base + offset + XHCI_LEGACY_CONTROL_OFFSET);
- val &= XHCI_LEGACY_DISABLE_SMI;
- val |= XHCI_LEGACY_SMI_EVENTS;
- writel(val, xdbc.xhci_base + offset + XHCI_LEGACY_CONTROL_OFFSET);
- }
- static int __init
- xdbc_alloc_ring(struct xdbc_segment *seg, struct xdbc_ring *ring)
- {
- seg->trbs = xdbc_get_page(&seg->dma);
- if (!seg->trbs)
- return -ENOMEM;
- ring->segment = seg;
- return 0;
- }
- static void __init xdbc_free_ring(struct xdbc_ring *ring)
- {
- struct xdbc_segment *seg = ring->segment;
- if (!seg)
- return;
- free_bootmem(seg->dma, PAGE_SIZE);
- ring->segment = NULL;
- }
- static void xdbc_reset_ring(struct xdbc_ring *ring)
- {
- struct xdbc_segment *seg = ring->segment;
- struct xdbc_trb *link_trb;
- memset(seg->trbs, 0, PAGE_SIZE);
- ring->enqueue = seg->trbs;
- ring->dequeue = seg->trbs;
- ring->cycle_state = 1;
- if (ring != &xdbc.evt_ring) {
- link_trb = &seg->trbs[XDBC_TRBS_PER_SEGMENT - 1];
- link_trb->field[0] = cpu_to_le32(lower_32_bits(seg->dma));
- link_trb->field[1] = cpu_to_le32(upper_32_bits(seg->dma));
- link_trb->field[3] = cpu_to_le32(TRB_TYPE(TRB_LINK)) | cpu_to_le32(LINK_TOGGLE);
- }
- }
- static inline void xdbc_put_utf16(u16 *s, const char *c, size_t size)
- {
- int i;
- for (i = 0; i < size; i++)
- s[i] = cpu_to_le16(c[i]);
- }
- static void xdbc_mem_init(void)
- {
- struct xdbc_ep_context *ep_in, *ep_out;
- struct usb_string_descriptor *s_desc;
- struct xdbc_erst_entry *entry;
- struct xdbc_strings *strings;
- struct xdbc_context *ctx;
- unsigned int max_burst;
- u32 string_length;
- int index = 0;
- u32 dev_info;
- xdbc_reset_ring(&xdbc.evt_ring);
- xdbc_reset_ring(&xdbc.in_ring);
- xdbc_reset_ring(&xdbc.out_ring);
- memset(xdbc.table_base, 0, PAGE_SIZE);
- memset(xdbc.out_buf, 0, PAGE_SIZE);
- /* Initialize event ring segment table: */
- xdbc.erst_size = 16;
- xdbc.erst_base = xdbc.table_base + index * XDBC_TABLE_ENTRY_SIZE;
- xdbc.erst_dma = xdbc.table_dma + index * XDBC_TABLE_ENTRY_SIZE;
- index += XDBC_ERST_ENTRY_NUM;
- entry = (struct xdbc_erst_entry *)xdbc.erst_base;
- entry->seg_addr = cpu_to_le64(xdbc.evt_seg.dma);
- entry->seg_size = cpu_to_le32(XDBC_TRBS_PER_SEGMENT);
- entry->__reserved_0 = 0;
- /* Initialize ERST registers: */
- writel(1, &xdbc.xdbc_reg->ersts);
- xdbc_write64(xdbc.erst_dma, &xdbc.xdbc_reg->erstba);
- xdbc_write64(xdbc.evt_seg.dma, &xdbc.xdbc_reg->erdp);
- /* Debug capability contexts: */
- xdbc.dbcc_size = 64 * 3;
- xdbc.dbcc_base = xdbc.table_base + index * XDBC_TABLE_ENTRY_SIZE;
- xdbc.dbcc_dma = xdbc.table_dma + index * XDBC_TABLE_ENTRY_SIZE;
- index += XDBC_DBCC_ENTRY_NUM;
- /* Popluate the strings: */
- xdbc.string_size = sizeof(struct xdbc_strings);
- xdbc.string_base = xdbc.table_base + index * XDBC_TABLE_ENTRY_SIZE;
- xdbc.string_dma = xdbc.table_dma + index * XDBC_TABLE_ENTRY_SIZE;
- strings = (struct xdbc_strings *)xdbc.string_base;
- index += XDBC_STRING_ENTRY_NUM;
- /* Serial string: */
- s_desc = (struct usb_string_descriptor *)strings->serial;
- s_desc->bLength = (strlen(XDBC_STRING_SERIAL) + 1) * 2;
- s_desc->bDescriptorType = USB_DT_STRING;
- xdbc_put_utf16(s_desc->wData, XDBC_STRING_SERIAL, strlen(XDBC_STRING_SERIAL));
- string_length = s_desc->bLength;
- string_length <<= 8;
- /* Product string: */
- s_desc = (struct usb_string_descriptor *)strings->product;
- s_desc->bLength = (strlen(XDBC_STRING_PRODUCT) + 1) * 2;
- s_desc->bDescriptorType = USB_DT_STRING;
- xdbc_put_utf16(s_desc->wData, XDBC_STRING_PRODUCT, strlen(XDBC_STRING_PRODUCT));
- string_length += s_desc->bLength;
- string_length <<= 8;
- /* Manufacture string: */
- s_desc = (struct usb_string_descriptor *)strings->manufacturer;
- s_desc->bLength = (strlen(XDBC_STRING_MANUFACTURER) + 1) * 2;
- s_desc->bDescriptorType = USB_DT_STRING;
- xdbc_put_utf16(s_desc->wData, XDBC_STRING_MANUFACTURER, strlen(XDBC_STRING_MANUFACTURER));
- string_length += s_desc->bLength;
- string_length <<= 8;
- /* String0: */
- strings->string0[0] = 4;
- strings->string0[1] = USB_DT_STRING;
- strings->string0[2] = 0x09;
- strings->string0[3] = 0x04;
- string_length += 4;
- /* Populate info Context: */
- ctx = (struct xdbc_context *)xdbc.dbcc_base;
- ctx->info.string0 = cpu_to_le64(xdbc.string_dma);
- ctx->info.manufacturer = cpu_to_le64(xdbc.string_dma + XDBC_MAX_STRING_LENGTH);
- ctx->info.product = cpu_to_le64(xdbc.string_dma + XDBC_MAX_STRING_LENGTH * 2);
- ctx->info.serial = cpu_to_le64(xdbc.string_dma + XDBC_MAX_STRING_LENGTH * 3);
- ctx->info.length = cpu_to_le32(string_length);
- /* Populate bulk out endpoint context: */
- max_burst = DEBUG_MAX_BURST(readl(&xdbc.xdbc_reg->control));
- ep_out = (struct xdbc_ep_context *)&ctx->out;
- ep_out->ep_info1 = 0;
- ep_out->ep_info2 = cpu_to_le32(EP_TYPE(BULK_OUT_EP) | MAX_PACKET(1024) | MAX_BURST(max_burst));
- ep_out->deq = cpu_to_le64(xdbc.out_seg.dma | xdbc.out_ring.cycle_state);
- /* Populate bulk in endpoint context: */
- ep_in = (struct xdbc_ep_context *)&ctx->in;
- ep_in->ep_info1 = 0;
- ep_in->ep_info2 = cpu_to_le32(EP_TYPE(BULK_IN_EP) | MAX_PACKET(1024) | MAX_BURST(max_burst));
- ep_in->deq = cpu_to_le64(xdbc.in_seg.dma | xdbc.in_ring.cycle_state);
- /* Set DbC context and info registers: */
- xdbc_write64(xdbc.dbcc_dma, &xdbc.xdbc_reg->dccp);
- dev_info = cpu_to_le32((XDBC_VENDOR_ID << 16) | XDBC_PROTOCOL);
- writel(dev_info, &xdbc.xdbc_reg->devinfo1);
- dev_info = cpu_to_le32((XDBC_DEVICE_REV << 16) | XDBC_PRODUCT_ID);
- writel(dev_info, &xdbc.xdbc_reg->devinfo2);
- xdbc.in_buf = xdbc.out_buf + XDBC_MAX_PACKET;
- xdbc.in_dma = xdbc.out_dma + XDBC_MAX_PACKET;
- }
- static void xdbc_do_reset_debug_port(u32 id, u32 count)
- {
- void __iomem *ops_reg;
- void __iomem *portsc;
- u32 val, cap_length;
- int i;
- cap_length = readl(xdbc.xhci_base) & 0xff;
- ops_reg = xdbc.xhci_base + cap_length;
- id--;
- for (i = id; i < (id + count); i++) {
- portsc = ops_reg + 0x400 + i * 0x10;
- val = readl(portsc);
- if (!(val & PORT_CONNECT))
- writel(val | PORT_RESET, portsc);
- }
- }
- static void xdbc_reset_debug_port(void)
- {
- u32 val, port_offset, port_count;
- int offset = 0;
- do {
- offset = xhci_find_next_ext_cap(xdbc.xhci_base, offset, XHCI_EXT_CAPS_PROTOCOL);
- if (!offset)
- break;
- val = readl(xdbc.xhci_base + offset);
- if (XHCI_EXT_PORT_MAJOR(val) != 0x3)
- continue;
- val = readl(xdbc.xhci_base + offset + 8);
- port_offset = XHCI_EXT_PORT_OFF(val);
- port_count = XHCI_EXT_PORT_COUNT(val);
- xdbc_do_reset_debug_port(port_offset, port_count);
- } while (1);
- }
- static void
- xdbc_queue_trb(struct xdbc_ring *ring, u32 field1, u32 field2, u32 field3, u32 field4)
- {
- struct xdbc_trb *trb, *link_trb;
- trb = ring->enqueue;
- trb->field[0] = cpu_to_le32(field1);
- trb->field[1] = cpu_to_le32(field2);
- trb->field[2] = cpu_to_le32(field3);
- trb->field[3] = cpu_to_le32(field4);
- ++(ring->enqueue);
- if (ring->enqueue >= &ring->segment->trbs[TRBS_PER_SEGMENT - 1]) {
- link_trb = ring->enqueue;
- if (ring->cycle_state)
- link_trb->field[3] |= cpu_to_le32(TRB_CYCLE);
- else
- link_trb->field[3] &= cpu_to_le32(~TRB_CYCLE);
- ring->enqueue = ring->segment->trbs;
- ring->cycle_state ^= 1;
- }
- }
- static void xdbc_ring_doorbell(int target)
- {
- writel(DOOR_BELL_TARGET(target), &xdbc.xdbc_reg->doorbell);
- }
- static int xdbc_start(void)
- {
- u32 ctrl, status;
- int ret;
- ctrl = readl(&xdbc.xdbc_reg->control);
- writel(ctrl | CTRL_DBC_ENABLE | CTRL_PORT_ENABLE, &xdbc.xdbc_reg->control);
- ret = handshake(&xdbc.xdbc_reg->control, CTRL_DBC_ENABLE, CTRL_DBC_ENABLE, 100000, 100);
- if (ret) {
- xdbc_trace("failed to initialize hardware\n");
- return ret;
- }
- /* Reset port to avoid bus hang: */
- if (xdbc.vendor == PCI_VENDOR_ID_INTEL)
- xdbc_reset_debug_port();
- /* Wait for port connection: */
- ret = handshake(&xdbc.xdbc_reg->portsc, PORTSC_CONN_STATUS, PORTSC_CONN_STATUS, 5000000, 100);
- if (ret) {
- xdbc_trace("waiting for connection timed out\n");
- return ret;
- }
- /* Wait for debug device to be configured: */
- ret = handshake(&xdbc.xdbc_reg->control, CTRL_DBC_RUN, CTRL_DBC_RUN, 5000000, 100);
- if (ret) {
- xdbc_trace("waiting for device configuration timed out\n");
- return ret;
- }
- /* Check port number: */
- status = readl(&xdbc.xdbc_reg->status);
- if (!DCST_DEBUG_PORT(status)) {
- xdbc_trace("invalid root hub port number\n");
- return -ENODEV;
- }
- xdbc.port_number = DCST_DEBUG_PORT(status);
- xdbc_trace("DbC is running now, control 0x%08x port ID %d\n",
- readl(&xdbc.xdbc_reg->control), xdbc.port_number);
- return 0;
- }
- static int xdbc_bulk_transfer(void *data, int size, bool read)
- {
- struct xdbc_ring *ring;
- struct xdbc_trb *trb;
- u32 length, control;
- u32 cycle;
- u64 addr;
- if (size > XDBC_MAX_PACKET) {
- xdbc_trace("bad parameter, size %d\n", size);
- return -EINVAL;
- }
- if (!(xdbc.flags & XDBC_FLAGS_INITIALIZED) ||
- !(xdbc.flags & XDBC_FLAGS_CONFIGURED) ||
- (!read && (xdbc.flags & XDBC_FLAGS_OUT_STALL)) ||
- (read && (xdbc.flags & XDBC_FLAGS_IN_STALL))) {
- xdbc_trace("connection not ready, flags %08x\n", xdbc.flags);
- return -EIO;
- }
- ring = (read ? &xdbc.in_ring : &xdbc.out_ring);
- trb = ring->enqueue;
- cycle = ring->cycle_state;
- length = TRB_LEN(size);
- control = TRB_TYPE(TRB_NORMAL) | TRB_IOC;
- if (cycle)
- control &= cpu_to_le32(~TRB_CYCLE);
- else
- control |= cpu_to_le32(TRB_CYCLE);
- if (read) {
- memset(xdbc.in_buf, 0, XDBC_MAX_PACKET);
- addr = xdbc.in_dma;
- xdbc.flags |= XDBC_FLAGS_IN_PROCESS;
- } else {
- memset(xdbc.out_buf, 0, XDBC_MAX_PACKET);
- memcpy(xdbc.out_buf, data, size);
- addr = xdbc.out_dma;
- xdbc.flags |= XDBC_FLAGS_OUT_PROCESS;
- }
- xdbc_queue_trb(ring, lower_32_bits(addr), upper_32_bits(addr), length, control);
- /*
- * Add a barrier between writes of trb fields and flipping
- * the cycle bit:
- */
- wmb();
- if (cycle)
- trb->field[3] |= cpu_to_le32(cycle);
- else
- trb->field[3] &= cpu_to_le32(~TRB_CYCLE);
- xdbc_ring_doorbell(read ? IN_EP_DOORBELL : OUT_EP_DOORBELL);
- return size;
- }
- static int xdbc_handle_external_reset(void)
- {
- int ret = 0;
- xdbc.flags = 0;
- writel(0, &xdbc.xdbc_reg->control);
- ret = handshake(&xdbc.xdbc_reg->control, CTRL_DBC_ENABLE, 0, 100000, 10);
- if (ret)
- goto reset_out;
- xdbc_mem_init();
- mmiowb();
- ret = xdbc_start();
- if (ret < 0)
- goto reset_out;
- xdbc_trace("dbc recovered\n");
- xdbc.flags |= XDBC_FLAGS_INITIALIZED | XDBC_FLAGS_CONFIGURED;
- xdbc_bulk_transfer(NULL, XDBC_MAX_PACKET, true);
- return 0;
- reset_out:
- xdbc_trace("failed to recover from external reset\n");
- return ret;
- }
- static int __init xdbc_early_setup(void)
- {
- int ret;
- writel(0, &xdbc.xdbc_reg->control);
- ret = handshake(&xdbc.xdbc_reg->control, CTRL_DBC_ENABLE, 0, 100000, 100);
- if (ret)
- return ret;
- /* Allocate the table page: */
- xdbc.table_base = xdbc_get_page(&xdbc.table_dma);
- if (!xdbc.table_base)
- return -ENOMEM;
- /* Get and store the transfer buffer: */
- xdbc.out_buf = xdbc_get_page(&xdbc.out_dma);
- if (!xdbc.out_buf)
- return -ENOMEM;
- /* Allocate the event ring: */
- ret = xdbc_alloc_ring(&xdbc.evt_seg, &xdbc.evt_ring);
- if (ret < 0)
- return ret;
- /* Allocate IN/OUT endpoint transfer rings: */
- ret = xdbc_alloc_ring(&xdbc.in_seg, &xdbc.in_ring);
- if (ret < 0)
- return ret;
- ret = xdbc_alloc_ring(&xdbc.out_seg, &xdbc.out_ring);
- if (ret < 0)
- return ret;
- xdbc_mem_init();
- mmiowb();
- ret = xdbc_start();
- if (ret < 0) {
- writel(0, &xdbc.xdbc_reg->control);
- return ret;
- }
- xdbc.flags |= XDBC_FLAGS_INITIALIZED | XDBC_FLAGS_CONFIGURED;
- xdbc_bulk_transfer(NULL, XDBC_MAX_PACKET, true);
- return 0;
- }
- int __init early_xdbc_parse_parameter(char *s)
- {
- unsigned long dbgp_num = 0;
- u32 bus, dev, func, offset;
- int ret;
- if (!early_pci_allowed())
- return -EPERM;
- if (strstr(s, "keep"))
- early_console_keep = true;
- if (xdbc.xdbc_reg)
- return 0;
- if (*s && kstrtoul(s, 0, &dbgp_num))
- dbgp_num = 0;
- pr_notice("dbgp_num: %lu\n", dbgp_num);
- /* Locate the host controller: */
- ret = xdbc_find_dbgp(dbgp_num, &bus, &dev, &func);
- if (ret) {
- pr_notice("failed to locate xhci host\n");
- return -ENODEV;
- }
- xdbc.vendor = read_pci_config_16(bus, dev, func, PCI_VENDOR_ID);
- xdbc.device = read_pci_config_16(bus, dev, func, PCI_DEVICE_ID);
- xdbc.bus = bus;
- xdbc.dev = dev;
- xdbc.func = func;
- /* Map the IO memory: */
- xdbc.xhci_base = xdbc_map_pci_mmio(bus, dev, func);
- if (!xdbc.xhci_base)
- return -EINVAL;
- /* Locate DbC registers: */
- offset = xhci_find_next_ext_cap(xdbc.xhci_base, 0, XHCI_EXT_CAPS_DEBUG);
- if (!offset) {
- pr_notice("xhci host doesn't support debug capability\n");
- early_iounmap(xdbc.xhci_base, xdbc.xhci_length);
- xdbc.xhci_base = NULL;
- xdbc.xhci_length = 0;
- return -ENODEV;
- }
- xdbc.xdbc_reg = (struct xdbc_regs __iomem *)(xdbc.xhci_base + offset);
- return 0;
- }
- int __init early_xdbc_setup_hardware(void)
- {
- int ret;
- if (!xdbc.xdbc_reg)
- return -ENODEV;
- xdbc_bios_handoff();
- raw_spin_lock_init(&xdbc.lock);
- ret = xdbc_early_setup();
- if (ret) {
- pr_notice("failed to setup the connection to host\n");
- xdbc_free_ring(&xdbc.evt_ring);
- xdbc_free_ring(&xdbc.out_ring);
- xdbc_free_ring(&xdbc.in_ring);
- if (xdbc.table_dma)
- free_bootmem(xdbc.table_dma, PAGE_SIZE);
- if (xdbc.out_dma)
- free_bootmem(xdbc.out_dma, PAGE_SIZE);
- xdbc.table_base = NULL;
- xdbc.out_buf = NULL;
- }
- return ret;
- }
- static void xdbc_handle_port_status(struct xdbc_trb *evt_trb)
- {
- u32 port_reg;
- port_reg = readl(&xdbc.xdbc_reg->portsc);
- if (port_reg & PORTSC_CONN_CHANGE) {
- xdbc_trace("connect status change event\n");
- /* Check whether cable unplugged: */
- if (!(port_reg & PORTSC_CONN_STATUS)) {
- xdbc.flags = 0;
- xdbc_trace("cable unplugged\n");
- }
- }
- if (port_reg & PORTSC_RESET_CHANGE)
- xdbc_trace("port reset change event\n");
- if (port_reg & PORTSC_LINK_CHANGE)
- xdbc_trace("port link status change event\n");
- if (port_reg & PORTSC_CONFIG_CHANGE)
- xdbc_trace("config error change\n");
- /* Write back the value to clear RW1C bits: */
- writel(port_reg, &xdbc.xdbc_reg->portsc);
- }
- static void xdbc_handle_tx_event(struct xdbc_trb *evt_trb)
- {
- size_t remain_length;
- u32 comp_code;
- int ep_id;
- comp_code = GET_COMP_CODE(le32_to_cpu(evt_trb->field[2]));
- remain_length = EVENT_TRB_LEN(le32_to_cpu(evt_trb->field[2]));
- ep_id = TRB_TO_EP_ID(le32_to_cpu(evt_trb->field[3]));
- switch (comp_code) {
- case COMP_SUCCESS:
- remain_length = 0;
- case COMP_SHORT_PACKET:
- break;
- case COMP_TRB_ERROR:
- case COMP_BABBLE_DETECTED_ERROR:
- case COMP_USB_TRANSACTION_ERROR:
- case COMP_STALL_ERROR:
- default:
- if (ep_id == XDBC_EPID_OUT)
- xdbc.flags |= XDBC_FLAGS_OUT_STALL;
- if (ep_id == XDBC_EPID_IN)
- xdbc.flags |= XDBC_FLAGS_IN_STALL;
- xdbc_trace("endpoint %d stalled\n", ep_id);
- break;
- }
- if (ep_id == XDBC_EPID_IN) {
- xdbc.flags &= ~XDBC_FLAGS_IN_PROCESS;
- xdbc_bulk_transfer(NULL, XDBC_MAX_PACKET, true);
- } else if (ep_id == XDBC_EPID_OUT) {
- xdbc.flags &= ~XDBC_FLAGS_OUT_PROCESS;
- } else {
- xdbc_trace("invalid endpoint id %d\n", ep_id);
- }
- }
- static void xdbc_handle_events(void)
- {
- struct xdbc_trb *evt_trb;
- bool update_erdp = false;
- u32 reg;
- u8 cmd;
- cmd = read_pci_config_byte(xdbc.bus, xdbc.dev, xdbc.func, PCI_COMMAND);
- if (!(cmd & PCI_COMMAND_MASTER)) {
- cmd |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY;
- write_pci_config_byte(xdbc.bus, xdbc.dev, xdbc.func, PCI_COMMAND, cmd);
- }
- if (!(xdbc.flags & XDBC_FLAGS_INITIALIZED))
- return;
- /* Handle external reset events: */
- reg = readl(&xdbc.xdbc_reg->control);
- if (!(reg & CTRL_DBC_ENABLE)) {
- if (xdbc_handle_external_reset()) {
- xdbc_trace("failed to recover connection\n");
- return;
- }
- }
- /* Handle configure-exit event: */
- reg = readl(&xdbc.xdbc_reg->control);
- if (reg & CTRL_DBC_RUN_CHANGE) {
- writel(reg, &xdbc.xdbc_reg->control);
- if (reg & CTRL_DBC_RUN)
- xdbc.flags |= XDBC_FLAGS_CONFIGURED;
- else
- xdbc.flags &= ~XDBC_FLAGS_CONFIGURED;
- }
- /* Handle endpoint stall event: */
- reg = readl(&xdbc.xdbc_reg->control);
- if (reg & CTRL_HALT_IN_TR) {
- xdbc.flags |= XDBC_FLAGS_IN_STALL;
- } else {
- xdbc.flags &= ~XDBC_FLAGS_IN_STALL;
- if (!(xdbc.flags & XDBC_FLAGS_IN_PROCESS))
- xdbc_bulk_transfer(NULL, XDBC_MAX_PACKET, true);
- }
- if (reg & CTRL_HALT_OUT_TR)
- xdbc.flags |= XDBC_FLAGS_OUT_STALL;
- else
- xdbc.flags &= ~XDBC_FLAGS_OUT_STALL;
- /* Handle the events in the event ring: */
- evt_trb = xdbc.evt_ring.dequeue;
- while ((le32_to_cpu(evt_trb->field[3]) & TRB_CYCLE) == xdbc.evt_ring.cycle_state) {
- /*
- * Add a barrier between reading the cycle flag and any
- * reads of the event's flags/data below:
- */
- rmb();
- switch ((le32_to_cpu(evt_trb->field[3]) & TRB_TYPE_BITMASK)) {
- case TRB_TYPE(TRB_PORT_STATUS):
- xdbc_handle_port_status(evt_trb);
- break;
- case TRB_TYPE(TRB_TRANSFER):
- xdbc_handle_tx_event(evt_trb);
- break;
- default:
- break;
- }
- ++(xdbc.evt_ring.dequeue);
- if (xdbc.evt_ring.dequeue == &xdbc.evt_seg.trbs[TRBS_PER_SEGMENT]) {
- xdbc.evt_ring.dequeue = xdbc.evt_seg.trbs;
- xdbc.evt_ring.cycle_state ^= 1;
- }
- evt_trb = xdbc.evt_ring.dequeue;
- update_erdp = true;
- }
- /* Update event ring dequeue pointer: */
- if (update_erdp)
- xdbc_write64(__pa(xdbc.evt_ring.dequeue), &xdbc.xdbc_reg->erdp);
- }
- static int xdbc_bulk_write(const char *bytes, int size)
- {
- int ret, timeout = 0;
- unsigned long flags;
- retry:
- if (in_nmi()) {
- if (!raw_spin_trylock_irqsave(&xdbc.lock, flags))
- return -EAGAIN;
- } else {
- raw_spin_lock_irqsave(&xdbc.lock, flags);
- }
- xdbc_handle_events();
- /* Check completion of the previous request: */
- if ((xdbc.flags & XDBC_FLAGS_OUT_PROCESS) && (timeout < 2000000)) {
- raw_spin_unlock_irqrestore(&xdbc.lock, flags);
- udelay(100);
- timeout += 100;
- goto retry;
- }
- if (xdbc.flags & XDBC_FLAGS_OUT_PROCESS) {
- raw_spin_unlock_irqrestore(&xdbc.lock, flags);
- xdbc_trace("previous transfer not completed yet\n");
- return -ETIMEDOUT;
- }
- ret = xdbc_bulk_transfer((void *)bytes, size, false);
- raw_spin_unlock_irqrestore(&xdbc.lock, flags);
- return ret;
- }
- static void early_xdbc_write(struct console *con, const char *str, u32 n)
- {
- static char buf[XDBC_MAX_PACKET];
- int chunk, ret;
- int use_cr = 0;
- if (!xdbc.xdbc_reg)
- return;
- memset(buf, 0, XDBC_MAX_PACKET);
- while (n > 0) {
- for (chunk = 0; chunk < XDBC_MAX_PACKET && n > 0; str++, chunk++, n--) {
- if (!use_cr && *str == '\n') {
- use_cr = 1;
- buf[chunk] = '\r';
- str--;
- n++;
- continue;
- }
- if (use_cr)
- use_cr = 0;
- buf[chunk] = *str;
- }
- if (chunk > 0) {
- ret = xdbc_bulk_write(buf, chunk);
- if (ret < 0)
- xdbc_trace("missed message {%s}\n", buf);
- }
- }
- }
- static struct console early_xdbc_console = {
- .name = "earlyxdbc",
- .write = early_xdbc_write,
- .flags = CON_PRINTBUFFER,
- .index = -1,
- };
- void __init early_xdbc_register_console(void)
- {
- if (early_console)
- return;
- early_console = &early_xdbc_console;
- if (early_console_keep)
- early_console->flags &= ~CON_BOOT;
- else
- early_console->flags |= CON_BOOT;
- register_console(early_console);
- }
- static void xdbc_unregister_console(void)
- {
- if (early_xdbc_console.flags & CON_ENABLED)
- unregister_console(&early_xdbc_console);
- }
- static int xdbc_scrub_function(void *ptr)
- {
- unsigned long flags;
- while (true) {
- raw_spin_lock_irqsave(&xdbc.lock, flags);
- xdbc_handle_events();
- if (!(xdbc.flags & XDBC_FLAGS_INITIALIZED)) {
- raw_spin_unlock_irqrestore(&xdbc.lock, flags);
- break;
- }
- raw_spin_unlock_irqrestore(&xdbc.lock, flags);
- schedule_timeout_interruptible(1);
- }
- xdbc_unregister_console();
- writel(0, &xdbc.xdbc_reg->control);
- xdbc_trace("dbc scrub function exits\n");
- return 0;
- }
- static int __init xdbc_init(void)
- {
- unsigned long flags;
- void __iomem *base;
- int ret = 0;
- u32 offset;
- if (!(xdbc.flags & XDBC_FLAGS_INITIALIZED))
- return 0;
- /*
- * It's time to shut down the DbC, so that the debug
- * port can be reused by the host controller:
- */
- if (early_xdbc_console.index == -1 ||
- (early_xdbc_console.flags & CON_BOOT)) {
- xdbc_trace("hardware not used anymore\n");
- goto free_and_quit;
- }
- base = ioremap_nocache(xdbc.xhci_start, xdbc.xhci_length);
- if (!base) {
- xdbc_trace("failed to remap the io address\n");
- ret = -ENOMEM;
- goto free_and_quit;
- }
- raw_spin_lock_irqsave(&xdbc.lock, flags);
- early_iounmap(xdbc.xhci_base, xdbc.xhci_length);
- xdbc.xhci_base = base;
- offset = xhci_find_next_ext_cap(xdbc.xhci_base, 0, XHCI_EXT_CAPS_DEBUG);
- xdbc.xdbc_reg = (struct xdbc_regs __iomem *)(xdbc.xhci_base + offset);
- raw_spin_unlock_irqrestore(&xdbc.lock, flags);
- kthread_run(xdbc_scrub_function, NULL, "%s", "xdbc");
- return 0;
- free_and_quit:
- xdbc_free_ring(&xdbc.evt_ring);
- xdbc_free_ring(&xdbc.out_ring);
- xdbc_free_ring(&xdbc.in_ring);
- free_bootmem(xdbc.table_dma, PAGE_SIZE);
- free_bootmem(xdbc.out_dma, PAGE_SIZE);
- writel(0, &xdbc.xdbc_reg->control);
- early_iounmap(xdbc.xhci_base, xdbc.xhci_length);
- return ret;
- }
- subsys_initcall(xdbc_init);
|