drivers/usbhost: Separate the xHCI driver from the PCI bus.

Nothing in the controller driver is PCI-specific beyond finding the
registers and the interrupt, so an SoC wiring an xHCI controller
directly could not use any of it.

usbhost_xhci.c is now the driver; usbhost_xhci_pci.c is the PCI
attachment, holding the ID table, the BAR mapping and the MSI-X vector.
include/nuttx/usb/xhci.h carries what passes between them: the register
base, a way to attach the interrupt, and a name to report the controller
by, since a system may have more than one.

The interrupt belongs to the bus: the bus attaches and detaches it, and
the driver never holds an interrupt number.

USBHOST_XHCI is the driver and is not selectable on its own;
USBHOST_XHCI_PCI selects it.  Another bus adds its own symbol beside it.

No functional change intended.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
This commit is contained in:
Justin Hammond 2026-08-16 16:09:13 +08:00 committed by Alan C. Assis
parent 65e835c779
commit 82fb0d8d06
7 changed files with 531 additions and 245 deletions

View file

@ -83,10 +83,14 @@ if(CONFIG_USBHOST)
list(APPEND SRCS usbhost_bthci.c)
endif()
if(CONFIG_USBHOST_XHCI_PCI)
if(CONFIG_USBHOST_XHCI)
list(APPEND SRCS usbhost_xhci.c usbhost_xhci_trace.c)
endif()
if(CONFIG_USBHOST_XHCI_PCI)
list(APPEND SRCS usbhost_xhci_pci.c)
endif()
# HCD debug/trace logic
if(CONFIG_USBHOST_TRACE)

View file

@ -813,15 +813,17 @@ config USBHOST_BTHCI
---help---
Select this option to build in support for USB Bluetooth HCI devices.
menuconfig USBHOST_XHCI_PCI
bool "USB xHCI PCI Host Driver Support"
config USBHOST_XHCI
bool
default n
depends on PCI && PCI_MSIX && USBHOST_WAITER && SCHED_HPWORK && SCHED_LPWORK
depends on USBHOST_WAITER && SCHED_HPWORK && SCHED_LPWORK
select USBHOST_HAVE_ASYNCH
---help---
USB xHCI PCI host driver support.
The xHCI controller driver itself, which is the same wherever the
controller is fitted. Selected by whichever bus found one: PCI
below, or an SoC that wires one in.
if USBHOST_XHCI_PCI
if USBHOST_XHCI
config USBHOST_XHCI_MAX_DEVS
int "xHCI maximum supported devices"
@ -830,6 +832,14 @@ config USBHOST_XHCI_MAX_DEVS
---help---
How many USB devices will be supported by xHCI driver.
endif # USBHOST_XHCI_PCI
endif # USBHOST_XHCI
menuconfig USBHOST_XHCI_PCI
bool "USB xHCI PCI Host Driver Support"
default n
depends on PCI && PCI_MSIX && USBHOST_WAITER && SCHED_HPWORK && SCHED_LPWORK
select USBHOST_XHCI
---help---
USB xHCI PCI host driver support.
endif # USBHOST

View file

@ -84,10 +84,14 @@ ifeq ($(CONFIG_USBHOST_BTHCI),y)
CSRCS += usbhost_bthci.c
endif
ifeq ($(CONFIG_USBHOST_XHCI_PCI),y)
ifeq ($(CONFIG_USBHOST_XHCI),y)
CSRCS += usbhost_xhci.c usbhost_xhci_trace.c
endif
ifeq ($(CONFIG_USBHOST_XHCI_PCI),y)
CSRCS += usbhost_xhci_pci.c
endif
# HCD debug/trace logic
ifeq ($(CONFIG_USBHOST_TRACE),y)

View file

@ -1,5 +1,5 @@
/****************************************************************************
* drivers/usbhost/usbhost_xhci_pci.c
* drivers/usbhost/usbhost_xhci.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
@ -36,8 +36,6 @@
#include <nuttx/addrenv.h>
#include <nuttx/spinlock.h>
#include <nuttx/pci/pci.h>
#include <nuttx/usb/usb.h>
#include <nuttx/usb/usbhost.h>
#include <nuttx/usb/usbhost_devaddr.h>
@ -69,13 +67,16 @@
#define XHCI_EVENT_MAX (232)
#define XHCI_TD_MAX (8)
/* Milliseconds allowed for the controller to halt. The specification
* asks for 16.
/* How long to give the controller to stop, in milliseconds. The
* specification asks for it within 16; this is generous.
*/
#define XHCI_HALT_TIMEOUT_MS (100)
/* Milliseconds allowed for a port to enable after reset. */
/* How long to give a port to come up after being reset, in milliseconds.
* USB 2.0 asks for the reset to be held 10ms and the port to be usable
* shortly after; this is generous.
*/
#define XHCI_PORT_RESET_MS (500)
#define XHCI_BUFSIZE (512)
@ -240,10 +241,11 @@ struct usbhost_xhci_s
struct xhci_trb_s cmdres; /* Command result */
struct xhci_ring_s cmd; /* Command ring handler */
/* PCI data */
/* Bus hookup, supplied by whoever found this controller */
FAR struct pci_device_s *pcidev; /* PCI device reference */
int irq; /* IRQ number used by the device */
FAR const struct xhci_bus_ops_s *ops; /* Bus operations */
FAR void *arg; /* Bus private data */
FAR const char *name; /* What to call this controller */
uint32_t pending; /* IRQ pending status */
struct work_s work; /* IRQ work */
struct work_s pscwork; /* Port status change work */
@ -482,48 +484,6 @@ static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv);
static int xhci_mem_free(FAR struct usbhost_xhci_s *priv);
static int xhci_hw_initialize(FAR struct usbhost_xhci_s *priv);
static int xhci_sw_initialize(FAR struct usbhost_xhci_s *priv);
static int pci_xhci_probe(FAR struct pci_device_s *dev);
static void pci_xhci_remove(FAR struct pci_device_s *dev);
/****************************************************************************
* Private Data
****************************************************************************/
/* PCI device table */
static const struct pci_device_id_s g_pci_xhci_id_table[] =
{
/* QEMU xHCI */
{
PCI_DEVICE(0x1b36, 0x000d),
.driver_data = 0
},
/* Intel Alder Lake USB 3.2 xHCI controller */
{
PCI_DEVICE(0x8086, 0x51ed),
.driver_data = 0
},
/* Intel Alder Lake-S USB 3.2 Gen 2x2 xHCI controller */
{
PCI_DEVICE(0x8086, 0x7ae0),
.driver_data = 0
},
{ }
};
/* PCI driver */
static struct pci_driver_s g_pci_xhci_drv =
{
.id_table = g_pci_xhci_id_table,
.probe = pci_xhci_probe,
.remove = pci_xhci_remove,
};
/****************************************************************************
* Private Functions
@ -723,7 +683,7 @@ static void xhci_door_putreg(FAR struct usbhost_xhci_s *priv,
static void xhci_dump_capa_reg(FAR struct usbhost_xhci_s *priv,
FAR const char *msg, unsigned int offset)
{
pciinfo("\t%s:\t\t0x%" PRIx32 "\n", msg, xhci_capa_getreg(priv, offset));
uinfo("\t%s:\t\t0x%" PRIx32 "\n", msg, xhci_capa_getreg(priv, offset));
}
/****************************************************************************
@ -733,7 +693,7 @@ static void xhci_dump_capa_reg(FAR struct usbhost_xhci_s *priv,
static void xhci_dump_oper_reg(FAR struct usbhost_xhci_s *priv,
FAR const char *msg, unsigned int offset)
{
pciinfo("\t%s:\t\t0x%" PRIx32 "\n", msg, xhci_oper_getreg(priv, offset));
uinfo("\t%s:\t\t0x%" PRIx32 "\n", msg, xhci_oper_getreg(priv, offset));
}
/****************************************************************************
@ -743,7 +703,7 @@ static void xhci_dump_oper_reg(FAR struct usbhost_xhci_s *priv,
static void xhci_dump_runt_reg(FAR struct usbhost_xhci_s *priv,
FAR const char *msg, unsigned int offset)
{
pciinfo("\t%s:\t\t0x%" PRIx32 "\n", msg, xhci_runt_getreg(priv, offset));
uinfo("\t%s:\t\t0x%" PRIx32 "\n", msg, xhci_runt_getreg(priv, offset));
}
/****************************************************************************
@ -755,9 +715,9 @@ static void xhci_dump_mem(FAR struct usbhost_xhci_s *priv,
{
int i;
pciinfo("Dump xHCI registers: %s\n", msg);
uinfo("Dump xHCI registers: %s\n", msg);
pciinfo("=== Host Controller Capability Registers ===\n");
uinfo("=== Host Controller Capability Registers ===\n");
xhci_dump_capa_reg(priv, "CAPLENGTH ", XHCI_CAPLENGTH);
xhci_dump_capa_reg(priv, "HCIVERSION ", XHCI_HCIVERSION);
xhci_dump_capa_reg(priv, "HCSPARAMS1 ", XHCI_HCSPARAMS1);
@ -768,7 +728,7 @@ static void xhci_dump_mem(FAR struct usbhost_xhci_s *priv,
xhci_dump_capa_reg(priv, "RTSOFF ", XHCI_RTSOFF);
xhci_dump_capa_reg(priv, "HCCPARAMS2 ", XHCI_HCCPARAMS2);
pciinfo("=== Host Controller Operational Registers ===\n");
uinfo("=== Host Controller Operational Registers ===\n");
xhci_dump_oper_reg(priv, "USBCMD ", XHCI_USBCMD);
xhci_dump_oper_reg(priv, "USBSTS ", XHCI_USBSTS);
xhci_dump_oper_reg(priv, "PAGESIZE ", XHCI_PAGESIZE);
@ -779,7 +739,7 @@ static void xhci_dump_mem(FAR struct usbhost_xhci_s *priv,
for (i = 0; i < priv->no_ports; i++)
{
pciinfo("port %d --------------------------------\n", i);
uinfo("port %d --------------------------------\n", i);
xhci_dump_oper_reg(priv, "PORTSC ", XHCI_PORTSC(i));
xhci_dump_oper_reg(priv, "PORTPMSC ", XHCI_PORTPMSC(i));
xhci_dump_oper_reg(priv, "PORTLI ", XHCI_PORTLI(i));
@ -787,7 +747,7 @@ static void xhci_dump_mem(FAR struct usbhost_xhci_s *priv,
/* Only one interrupter used */
pciinfo("=== Host Controller Runtime Registers ===\n");
uinfo("=== Host Controller Runtime Registers ===\n");
xhci_dump_runt_reg(priv, "MFINDEX ", XHCI_MFINDEX);
xhci_dump_runt_reg(priv, "IMAN(0) ", XHCI_IMAN(0));
xhci_dump_runt_reg(priv, "IMOD(0) ", XHCI_IMOD(0));
@ -1097,7 +1057,7 @@ static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv)
int ret;
int i;
pciinfo("Start controller\n");
uinfo("Start controller\n");
/* Reset controller before writing any Operational or Runtime registers */
@ -1134,14 +1094,14 @@ static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv)
ret = xhci_ring_init(&priv->cmd, XHCI_CMD_MAX);
if (ret < 0)
{
pcierr("cmd ring init failed\n");
uerr("cmd ring init failed\n");
return ret;
}
ret = xhci_ring_init(&priv->evnt, XHCI_EVENT_MAX);
if (ret < 0)
{
pcierr("event ring init failed\n");
uerr("event ring init failed\n");
return ret;
}
@ -1216,7 +1176,7 @@ static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv)
if (ret != OK)
{
pcierr("Can't start controller!");
uerr("Can't start controller!");
return ret;
}
@ -1273,7 +1233,7 @@ static int xhci_ctrl_halt(FAR struct usbhost_xhci_s *priv)
up_udelay(1000);
}
pcierr("controller will not halt, USBSTS %08" PRIx32 "\n", regval);
uerr("controller will not halt, USBSTS %08" PRIx32 "\n", regval);
return -EAGAIN;
}
@ -1394,8 +1354,8 @@ static int xhci_port_enable(FAR struct usbhost_xhci_s *priv,
if ((regval & XHCI_PORTSC_PED) == 0)
{
pcierr("port %d will not enable, PORTSC %08" PRIx32 "\n", rhpndx,
regval);
uerr("port %d will not enable, PORTSC %08" PRIx32 "\n", rhpndx,
regval);
return -ETIMEDOUT;
}
}
@ -1443,7 +1403,7 @@ static int xhci_port_enable(FAR struct usbhost_xhci_s *priv,
default:
{
pcierr("speed = 0x%x\n", speed);
uerr("speed = 0x%x\n", speed);
hport->speed = USB_SPEED_UNKNOWN;
return -EINVAL;
}
@ -1746,7 +1706,7 @@ static int xhci_device_init(FAR struct usbhost_xhci_s *priv,
ret = xhci_ring_init(&rhport->ep0.td, XHCI_TD_MAX);
if (ret < 0)
{
pcierr("ep0 ring init failed\n");
uerr("ep0 ring init failed\n");
return ret;
}
@ -1772,7 +1732,7 @@ static int xhci_device_init(FAR struct usbhost_xhci_s *priv,
ret = xhci_address_set(priv, rhport, false);
if (ret < 0)
{
pcierr("failed to set address %d\n", ret);
uerr("failed to set address %d\n", ret);
return ret;
}
@ -1803,7 +1763,7 @@ static int xhci_device_deinit(FAR struct usbhost_xhci_s *priv,
ret = xhci_cmd_slotdis(priv, slot);
if (ret < 0)
{
pcierr("xhci_cmd_slotdis failed %d\n", ret);
uerr("xhci_cmd_slotdis failed %d\n", ret);
}
/* Clear DCBAA entry for this slot */
@ -1948,7 +1908,7 @@ static int xhci_command(FAR struct usbhost_xhci_s *priv,
}
else
{
pcierr("event CC = %d\n", XHCI_TRB_D1_CC_GET(trb->d1));
uerr("event CC = %d\n", XHCI_TRB_D1_CC_GET(trb->d1));
ret = -EIO;
}
@ -2768,7 +2728,7 @@ static void xhci_transfer_complete(FAR struct usbhost_xhci_s *priv,
{
/* Report error */
pcierr("transfer CC = %d\n", ret);
uerr("transfer CC = %d\n", ret);
epinfo->status = ret;
epinfo->result = -EIO;
}
@ -2903,7 +2863,7 @@ static int xhci_events_poll(FAR struct usbhost_xhci_s *priv)
default:
{
pciinfo("ignored event %d\n", type);
uinfo("ignored event %d\n", type);
break;
}
}
@ -2950,28 +2910,28 @@ static void xhci_interrupt_work(FAR void *arg)
{
/* Handled as event in xhci_events_poll() */
pciinfo("Port Change Detect\n");
uinfo("Port Change Detect\n");
}
/* Host Controller Halted */
if (priv->pending & XHCI_USBSTS_HCH)
{
pciinfo("Host Controller Halted\n");
uinfo("Host Controller Halted\n");
}
/* Host System Error */
if (priv->pending & XHCI_USBSTS_HSE)
{
pciinfo("Host System Error\n");
uinfo("Host System Error\n");
}
/* Host Controller Error */
if (priv->pending & XHCI_USBSTS_HCE)
{
pciinfo("Host Controller Error\n");
uinfo("Host Controller Error\n");
}
/* ACK interrupts */
@ -3169,7 +3129,7 @@ static int xhci_rh_enumerate(FAR struct usbhost_connection_s *conn,
{
/* No, return an error */
pcierr("not connected\n");
uerr("not connected\n");
return -ENODEV;
}
@ -3178,7 +3138,7 @@ static int xhci_rh_enumerate(FAR struct usbhost_connection_s *conn,
ret = xhci_port_enable(priv, hport);
if (ret < 0)
{
pcierr("Failed to enable port %d\n", ret);
uerr("Failed to enable port %d\n", ret);
return ret;
}
@ -3187,7 +3147,7 @@ static int xhci_rh_enumerate(FAR struct usbhost_connection_s *conn,
ret = xhci_device_init(priv, rhport);
if (ret < 0)
{
pcierr("Failed to initialize device %d\n", ret);
uerr("Failed to initialize device %d\n", ret);
return ret;
}
@ -3361,7 +3321,7 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr,
#ifdef CONFIG_USBHOST_TRACE
usbhost_vtrace2(XHCI_VTRACE2_EPALLOC, epdesc->addr, epdesc->xfrtype);
#else
pciinfo("EP%d DIR=%s FA=%08x TYPE=%d Interval=%d MaxPacket=%d\n",
uinfo("EP%d DIR=%s FA=%08x TYPE=%d Interval=%d MaxPacket=%d\n",
epdesc->addr, epdesc->in ? "IN" : "OUT", hport->funcaddr,
epdesc->xfrtype, epdesc->interval, epdesc->mxpacketsize);
#endif
@ -3401,7 +3361,7 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr,
ret = xhci_ring_init(&epinfo->td, XHCI_TD_MAX);
if (ret < 0)
{
pcierr("ep ring init failed\n");
uerr("ep ring init failed\n");
return ret;
}
@ -3495,7 +3455,7 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr,
up_addrenv_va_to_pa(dev->input), false);
if (ret < 0)
{
pcierr("failed to configure EP %d\n", ret);
uerr("failed to configure EP %d\n", ret);
return ret;
}
@ -3831,7 +3791,7 @@ static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr,
ret = xhci_control_setup(rhport, ep0info, req, buffer, len);
if (ret < 0)
{
pcierr("ERROR: xhci_control_setup failed: %d\n", ret);
uerr("ERROR: xhci_control_setup failed: %d\n", ret);
goto errout_with_iocwait;
}
@ -4308,7 +4268,7 @@ static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv)
regval = xhci_capa_getreg(priv, XHCI_HCCPARAMS1);
if (regval & XHCI_HCCPARAMS1_CSZ)
{
pcierr("Only 32 byte Context data structures supported!\n");
uerr("Only 32 byte Context data structures supported!\n");
return -EIO;
}
@ -4325,7 +4285,7 @@ static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv)
priv->no_slots = CONFIG_USBHOST_XHCI_MAX_DEVS;
}
pciinfo("no slots = %d, no ports = %d\n",
uinfo("no slots = %d, no ports = %d\n",
priv->no_slots, priv->no_ports);
/* Check if valid */
@ -4340,11 +4300,11 @@ static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv)
regval = xhci_capa_getreg(priv, XHCI_HCSPARAMS2);
priv->no_scratch = XHCI_HCSPARAMS2_MAXSPB(regval);
pciinfo("no scratch = %d\n", priv->no_scratch);
uinfo("no scratch = %d\n", priv->no_scratch);
priv->no_erst = 1 << XHCI_HCSPARAMS2_ERST(regval);
pciinfo("no_erst = %d\n", priv->no_erst);
uinfo("no_erst = %d\n", priv->no_erst);
/* Limit event ring segment table to 1 */
@ -4353,7 +4313,7 @@ static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv)
priv->no_erst = XHCI_MAX_ERST;
}
pciinfo("no erst = %d\n", priv->no_erst);
uinfo("no erst = %d\n", priv->no_erst);
return OK;
}
@ -4362,41 +4322,14 @@ static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv)
* Name: xhci_irq_initialize
*
* Description:
* Initialize xHCI interrupts - require MSI-X support.
* Ask the bus this controller was found on for its interrupt. See
* struct xhci_bus_ops_s in include/nuttx/usb/xhci.h.
*
****************************************************************************/
static int xhci_irq_initialize(FAR struct usbhost_xhci_s *priv)
{
int ret;
/* Allocate MSI */
ret = pci_alloc_irq(priv->pcidev, &priv->irq, 1);
if (ret != 1)
{
pcierr("Failed to allocate MSI %d\n", ret);
return ret;
}
/* Attach IRQ */
irq_attach(priv->irq, xhci_interrupt, priv);
/* Connect MSI-X */
ret = pci_connect_irq(priv->pcidev, &priv->irq, 1);
if (ret != OK)
{
pcierr("Failed to connect MSI %d\n", ret);
pci_release_irq(priv->pcidev, &priv->irq, 1);
return -ENOTSUP;
}
up_enable_irq(priv->irq);
return OK;
return priv->ops->irq_attach(priv->arg, xhci_interrupt, priv);
}
/****************************************************************************
@ -4422,7 +4355,7 @@ static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv)
priv->pg_sb = kmm_memalign(XHCI_BUF_ALIGN, tmp);
if (!priv->pg_sb)
{
pcierr("pg_sb malloc failed\n");
uerr("pg_sb malloc failed\n");
return -ENOMEM;
}
@ -4437,7 +4370,7 @@ static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv)
kmm_memalign(XHCI_PAGE_SIZE, XHCI_PAGE_SIZE));
if (!priv->pg_sb[i])
{
pcierr("pg_sb[i] malloc failed\n");
uerr("pg_sb[i] malloc failed\n");
return -ENOMEM;
}
@ -4455,7 +4388,7 @@ static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv)
priv->pg_ctx = kmm_memalign(XHCI_BUF_ALIGN, tmp);
if (!priv->pg_ctx)
{
pcierr("pg_ctx malloc failed\n");
uerr("pg_ctx malloc failed\n");
return -ENOMEM;
}
@ -4469,7 +4402,7 @@ static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv)
priv->pg_erst = kmm_memalign(XHCI_BUF_ALIGN, tmp);
if (!priv->pg_erst)
{
pcierr("priv->pg_erst malloc failed\n");
uerr("priv->pg_erst malloc failed\n");
return -ENOMEM;
}
@ -4480,7 +4413,7 @@ static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv)
priv->rhport = kmm_zalloc(priv->no_ports * sizeof(struct xhci_rhport_s));
if (!priv->rhport)
{
pcierr("rhport zalloc failed!\n");
uerr("rhport zalloc failed!\n");
return -ENOMEM;
}
@ -4489,7 +4422,7 @@ static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv)
priv->devs = kmm_zalloc(priv->no_slots * sizeof(struct xhci_dev_s));
if (!priv->devs)
{
pcierr("devs zalloc failed!\n");
uerr("devs zalloc failed!\n");
return -ENOMEM;
}
@ -4502,7 +4435,7 @@ static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv)
priv->devs[i].ctx = kmm_zalloc(sizeof(struct xhci_dev_ctx_s));
if (!priv->devs[i].ctx)
{
pcierr("dev ctx zalloc failed!\n");
uerr("dev ctx zalloc failed!\n");
return -ENOMEM;
}
@ -4514,7 +4447,7 @@ static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv)
sizeof(struct xhci_input_dev_ctx_s));
if (!priv->devs[i].input)
{
pcierr("dev input zalloc failed!\n");
uerr("dev input zalloc failed!\n");
return -ENOMEM;
}
@ -4592,7 +4525,7 @@ static int xhci_hw_initialize(FAR struct usbhost_xhci_s *priv)
ret = xhci_bios_wait(priv);
if (ret < 0)
{
pcierr("Failed to get xhci controller!\n");
uerr("Failed to get xhci controller!\n");
goto errout;
}
@ -4733,66 +4666,67 @@ static inline int xhci_sw_initialize(FAR struct usbhost_xhci_s *priv)
}
/****************************************************************************
* Name: pci_xhci_probe
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: xhci_initialize
*
* Description:
* Initialize PCI device.
* Bring up an xHCI controller and start watching its root hub ports.
*
* The caller has already found the controller and knows how to reach it;
* everything this function does from here on is described by the xHCI
* specification and is the same on any bus.
*
* Input Parameters:
* base - Where the controller's register block starts. The capability
* registers are at the beginning of it and say where the rest are.
* ops - How to reach the interrupt this controller raises
* arg - Opaque value passed back to ops
*
* Returned Value:
* A connection to hand to usbhost_waiter_initialize() or to drive
* directly; NULL on failure.
*
****************************************************************************/
static int pci_xhci_probe(FAR struct pci_device_s *dev)
FAR struct usbhost_connection_s *
xhci_initialize(FAR const char *name, uintptr_t base,
FAR const struct xhci_bus_ops_s *ops, FAR void *arg)
{
FAR struct usbhost_conn_xhci_s *conn = NULL;
FAR struct usbhost_xhci_s *priv = NULL;
int ret = -ENOMEM;
int ret;
/* Init PCI bus */
pci_set_master(dev);
pciinfo("Enabled bus mastering\n");
pci_enable_device(dev);
pciinfo("Enabled memory resources\n");
/* Allocate connection structure */
DEBUGASSERT(name != NULL && base != 0 && ops != NULL &&
ops->irq_attach != NULL);
conn = kmm_zalloc(sizeof(struct usbhost_conn_xhci_s));
if (!conn)
if (conn == NULL)
{
pcierr("zalloc failed!\n");
goto errout;
return NULL;
}
/* Allocate the driver structure */
priv = kmm_zalloc(sizeof(struct usbhost_xhci_s));
if (!priv)
if (priv == NULL)
{
pcierr("zalloc failed!\n");
goto errout;
kmm_free(conn);
return NULL;
}
/* Initialize connection data */
conn->conn.wait = xhci_wait;
conn->conn.enumerate = xhci_enumerate;
conn->priv = priv;
/* Connect PCI handler */
priv->name = name;
priv->ops = ops;
priv->arg = arg;
priv->base = base;
priv->pcidev = dev;
dev->priv = conn;
/* Get base address - BAR 0 */
priv->base = (uintptr_t)pci_map_bar(dev, 0);
if (!priv->base)
{
pcierr("Not found BAR 0!\n");
ret = -EIO;
goto errout;
}
/* Get register address */
/* The capability registers are the only ones at a known offset. Every
* other block is where they say it is.
*/
priv->capa_base = priv->base;
priv->oper_base = priv->base + xhci_capa_getreg_1b(priv, XHCI_CAPLENGTH);
@ -4801,26 +4735,20 @@ static int pci_xhci_probe(FAR struct pci_device_s *dev)
usbhost_vtrace1(XHCI_VTRACE1_INITIALIZING, 0);
/* Initialize HW */
ret = xhci_hw_initialize(priv);
if (ret < 0)
{
pcierr("failed to initialize HW!\n");
uerr("failed to initialize HW: %d\n", ret);
goto errout;
}
/* Initialize SW */
ret = xhci_sw_initialize(priv);
if (ret < 0)
{
pcierr("failed to initialize SW!\n");
uerr("failed to initialize SW: %d\n", ret);
goto errout;
}
/* Start controller */
ret = xhci_ctrl_start(priv);
if (ret < 0)
{
@ -4829,106 +4757,59 @@ static int pci_xhci_probe(FAR struct pci_device_s *dev)
}
#ifdef CONFIG_DEBUG_USB_INFO
/* Dump xhci registers */
xhci_dump_mem(priv, "after init");
#endif
/* If there is a USB device in the slot at power up, then we will not
* get the status change interrupt to signal us that the device is
* connected. We need to set the initial connected state accordingly.
/* A device already in a port at power up produces no status change
* interrupt to tell us it is there, so look for one.
*/
xhci_probe_ports(priv);
usbhost_vtrace1(XHCI_VTRACE1_INITIALIZING, 0);
/* Initialize waiter */
ret = usbhost_waiter_initialize(&conn->conn);
if (ret < 0)
{
pcierr("failed to initialize waiter!\n");
uerr("failed to initialize waiter: %d\n", ret);
goto errout;
}
/* Store waiter PID */
conn->pid = ret;
return OK;
return &conn->conn;
errout:
/* Free allocated xhci buffers */
xhci_mem_free(priv);
/* Free allocated data */
kmm_free(conn);
kmm_free(priv);
return ret;
return NULL;
}
/****************************************************************************
* Name: pci_xhci_remove
* Name: xhci_uninitialize
*
* Description:
* Remove PCI device.
* Stop watching a controller's ports and give back everything
* xhci_initialize() took.
*
****************************************************************************/
static void pci_xhci_remove(FAR struct pci_device_s *dev)
void xhci_uninitialize(FAR struct usbhost_connection_s *conn)
{
FAR struct usbhost_conn_xhci_s *conn = dev->priv;
FAR struct usbhost_xhci_s *priv = conn->priv;
FAR struct usbhost_conn_xhci_s *xconn = XHCI_XCONN_FROM_CONN(conn);
FAR struct usbhost_xhci_s *priv;
/* Free xhci interrupts */
DEBUGASSERT(conn != NULL);
priv = xconn->priv;
irq_detach(priv->irq);
pci_release_irq(dev, &priv->irq, 1);
/* Stop the waiter before the memory it walks goes away */
/* Disable PCI devicve */
kthread_delete(xconn->pid);
pci_clear_master(dev);
pci_disable_device(dev);
/* Delete waiter thread */
kthread_delete(conn->pid);
/* Free xhci buffers */
priv->ops->irq_detach(priv->arg);
xhci_mem_free(priv);
/* Free driver data */
kmm_free(conn);
kmm_free(priv);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: pci_xhci_init
*
* Description:
* Initialize the USB host xHCI as PCI device.
*
* Input Parameters:
* None
*
* Returned Value:
* On success this function will return zero (OK); A negated errno value
* will be returned on failure.
*
****************************************************************************/
int pci_xhci_init(void)
{
return pci_register_driver(&g_pci_xhci_drv);
kmm_free(xconn);
}

View file

@ -27,6 +27,10 @@
#include <nuttx/config.h>
#include <stdint.h>
#include <nuttx/usb/xhci.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/

View file

@ -0,0 +1,278 @@
/****************************************************************************
* drivers/usbhost/usbhost_xhci_pci.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/* Finding an xHCI controller on a PCI bus.
*
* The controller itself is described by its specification and driven by
* usbhost_xhci.c, which is the same code wherever the part is fitted. This
* file is only the part that is true of PCI and of nothing else: which
* device identifiers to answer to, how to switch a device on and find its
* register window, and how its interrupt is arranged.
*/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/arch.h>
#include <nuttx/debug.h>
#include <nuttx/irq.h>
#include <nuttx/kmalloc.h>
#include <nuttx/pci/pci.h>
#include <nuttx/usb/usbhost.h>
#include "usbhost_xhci.h"
/****************************************************************************
* Private Types
****************************************************************************/
/* What this file needs to remember about a controller, which is only the
* things the PCI layer will ask for again when the device goes away.
*/
struct pci_xhci_s
{
FAR struct pci_device_s *dev; /* The device we were given */
FAR struct usbhost_connection_s *conn; /* What the controller gave back */
int irq; /* Allocated MSI-X vector */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int pci_xhci_irq_attach(FAR void *arg, xcpt_t handler,
FAR void *priv);
static void pci_xhci_irq_detach(FAR void *arg);
static int pci_xhci_probe(FAR struct pci_device_s *dev);
static void pci_xhci_remove(FAR struct pci_device_s *dev);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct xhci_bus_ops_s g_pci_xhci_ops =
{
.irq_attach = pci_xhci_irq_attach,
.irq_detach = pci_xhci_irq_detach,
};
/* PCI device table */
static const struct pci_device_id_s g_pci_xhci_id_table[] =
{
/* QEMU xHCI */
{
PCI_DEVICE(0x1b36, 0x000d),
.driver_data = 0
},
/* Intel Alder Lake USB 3.2 xHCI controller */
{
PCI_DEVICE(0x8086, 0x51ed),
.driver_data = 0
},
/* Intel Alder Lake-S USB 3.2 Gen 2x2 xHCI controller */
{
PCI_DEVICE(0x8086, 0x7ae0),
.driver_data = 0
},
{ }
};
/* PCI driver */
static struct pci_driver_s g_pci_xhci_drv =
{
.id_table = g_pci_xhci_id_table,
.probe = pci_xhci_probe,
.remove = pci_xhci_remove,
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: pci_xhci_irq_attach
*
* Description:
* Give the controller an interrupt. On PCI that means asking for a
* message rather than finding a wire, so the vector is allocated here and
* only then attached.
*
****************************************************************************/
static int pci_xhci_irq_attach(FAR void *arg, xcpt_t handler, FAR void *priv)
{
FAR struct pci_xhci_s *pcix = arg;
int ret;
ret = pci_alloc_irq(pcix->dev, &pcix->irq, 1);
if (ret != 1)
{
pcierr("Failed to allocate MSI %d\n", ret);
return ret;
}
irq_attach(pcix->irq, handler, priv);
ret = pci_connect_irq(pcix->dev, &pcix->irq, 1);
if (ret != OK)
{
pcierr("Failed to connect MSI %d\n", ret);
pci_release_irq(pcix->dev, &pcix->irq, 1);
return -ENOTSUP;
}
up_enable_irq(pcix->irq);
return OK;
}
/****************************************************************************
* Name: pci_xhci_irq_detach
****************************************************************************/
static void pci_xhci_irq_detach(FAR void *arg)
{
FAR struct pci_xhci_s *pcix = arg;
up_disable_irq(pcix->irq);
irq_detach(pcix->irq);
pci_release_irq(pcix->dev, &pcix->irq, 1);
}
/****************************************************************************
* Name: pci_xhci_probe
*
* Description:
* Switch on a PCI xHCI controller and hand it to the controller driver.
*
****************************************************************************/
static int pci_xhci_probe(FAR struct pci_device_s *dev)
{
FAR struct pci_xhci_s *pcix;
uintptr_t base;
int ret;
pcix = kmm_zalloc(sizeof(struct pci_xhci_s));
if (pcix == NULL)
{
return -ENOMEM;
}
pcix->dev = dev;
dev->priv = pcix;
/* The controller has to be able to reach memory on its own, and its
* registers have to be answerable, before either is used.
*/
pci_set_master(dev);
pciinfo("Enabled bus mastering\n");
pci_enable_device(dev);
pciinfo("Enabled memory resources\n");
base = (uintptr_t)pci_map_bar(dev, 0);
if (base == 0)
{
pcierr("Not found BAR 0!\n");
ret = -EIO;
goto errout;
}
pcix->conn = xhci_initialize("usb", base, &g_pci_xhci_ops, pcix);
if (pcix->conn == NULL)
{
pcierr("xhci_initialize failed\n");
ret = -EIO;
goto errout;
}
return OK;
errout:
pci_clear_master(dev);
pci_disable_device(dev);
kmm_free(pcix);
return ret;
}
/****************************************************************************
* Name: pci_xhci_remove
****************************************************************************/
static void pci_xhci_remove(FAR struct pci_device_s *dev)
{
FAR struct pci_xhci_s *pcix = dev->priv;
if (pcix == NULL)
{
return;
}
xhci_uninitialize(pcix->conn);
pci_clear_master(dev);
pci_disable_device(dev);
dev->priv = NULL;
kmm_free(pcix);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: pci_xhci_init
*
* Description:
* Initialize the USB host xHCI as PCI device.
*
* Input Parameters:
* None
*
* Returned Value:
* On success this function will return zero (OK); A negated errno value
* will be returned on failure.
*
****************************************************************************/
int pci_xhci_init(void)
{
return pci_register_driver(&g_pci_xhci_drv);
}

105
include/nuttx/usb/xhci.h Normal file
View file

@ -0,0 +1,105 @@
/****************************************************************************
* include/nuttx/usb/xhci.h
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#ifndef __INCLUDE_NUTTX_USB_XHCI_H
#define __INCLUDE_NUTTX_USB_XHCI_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdbool.h>
#include <stdint.h>
#include <nuttx/irq.h>
#include <nuttx/usb/usbhost.h>
/****************************************************************************
* Public Types
****************************************************************************/
/* How the controller was found *********************************************/
/* What a controller cannot work out about itself.
*
* xHCI is the same silicon on PCI or wired into an SoC, and everything the
* specification describes is reached through the register block. What
* differs is how the interrupt arrives: PCI needs a message the device is
* configured to send, a memory mapped bus has a wire the interrupt
* controller already knows. The bus answers that, and nothing else.
*/
struct xhci_bus_ops_s
{
/* Attach the handler and make interrupts start arriving. Enabling
* belongs here rather than in the controller driver because on some
* buses attaching and enabling are one operation.
*/
CODE int (*irq_attach)(FAR void *arg, xcpt_t handler, FAR void *priv);
/* Undo it, and release anything the bus allocated to make it work */
CODE void (*irq_detach)(FAR void *arg);
};
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
/****************************************************************************
* Name: xhci_initialize
*
* Description:
* Bring up an xHCI controller and start watching its root hub ports.
*
* Input Parameters:
* name - What to call this controller when reporting what is attached to
* it, since a system may have more than one and "port 1" alone
* does not say which
* base - Where the controller's register block starts
* ops - How to reach the interrupt this controller raises
* arg - Opaque value passed back to ops
*
* Returned Value:
* A connection to hand to usbhost_waiter_initialize(); NULL on failure.
*
****************************************************************************/
FAR struct usbhost_connection_s *
xhci_initialize(FAR const char *name, uintptr_t base,
FAR const struct xhci_bus_ops_s *ops, FAR void *arg);
/****************************************************************************
* Name: xhci_uninitialize
*
* Description:
* Stop watching a controller's ports and give back everything
* xhci_initialize() took.
*
****************************************************************************/
void xhci_uninitialize(FAR struct usbhost_connection_s *conn);
#endif /* __INCLUDE_NUTTX_USB_XHCI_H */