/**************************************************************************** * drivers/usbhost/usbhost_xhci_pci.c * * 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. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbhost_xhci.h" #include "usbhost_xhci_trace.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Pre-requisites */ #if CONFIG_USBHOST_XHCI_MAX_DEVS > XHCI_MAX_DEVS # error Invalid value for CONFIG_USBHOST_XHCI_MAX_DEVS #endif /* USB HUB support is not yet implemented */ #ifdef CONFIG_USBHOST_HUB # error XHCI USB HUB support is not yet implemented #endif /* Some constants for this implementation */ #define XHCI_MAX_ERST (1) #define XHCI_CMD_MAX (16) #define XHCI_EVENT_MAX (232) #define XHCI_TD_MAX (8) #define XHCI_BUFSIZE (512) /* Port numbers macros */ #define HPNDX(hp) ((hp)->port) #define HPORT(hp) (HPNDX(hp) + 1) #define RHPNDX(rh) ((rh)->hport.hport.port) #define RHPORT(rh) (RHPNDX(rh) + 1) /* Other helper macros */ #define XHCI_XCONN_FROM_CONN(c) ((FAR struct usbhost_conn_xhci_s *)c) #define XHCI_PRIV_FROM_CONN(c) (XHCI_XCONN_FROM_CONN(c)->priv) #define XHCI_RHPORT_FROM_DRVR(d) ((FAR struct xhci_rhport_s *)d) #define XHCI_PRIV_FROM_RHPORT(r) (r->priv) #define XHCI_PRIV_FROM_DRVR(d) (XHCI_PRIV_FROM_RHPORT(XHCI_RHPORT_FROM_DRVR(d))) /**************************************************************************** * Private Types ****************************************************************************/ /* USB device state * * Reference: * - 4.5.3: Slot States */ enum xhci_slot_e { XHCI_SLOT_DISABLED, XHCI_SLOT_ENABLED, XHCI_SLOT_DEFAULT, XHCI_SLOT_ADDRESSED, XHCI_SLOT_CONFIGURED, }; /* Ring state */ struct xhci_ring_s { FAR struct xhci_trb_s *ring; /* Ring */ size_t i; /* Ring pointer */ size_t len; /* Ring length */ bool ccs; /* Consumer Cycle State */ }; /* EP info */ struct xhci_epinfo_s { uint8_t epno:7; /* Endpoint number */ uint8_t dirin:1; /* 1:IN endpoint 0:OUT endpoint */ uint8_t toggle:1; /* Next data toggle */ #ifndef CONFIG_USBHOST_INT_DISABLE uint8_t interval; /* Polling interval */ #endif uint8_t devaddr; /* Device address returned from xHCI */ uint8_t status; /* Retained token status bits (for debug purposes) */ bool iocwait; /* TRUE: Thread is waiting for transfer completion */ uint8_t xfrtype:2; /* See USB_EP_ATTR_XFER_* definitions in usb.h */ int result; /* The result of the transfer */ size_t xfrd; /* On completion, will hold the number of bytes transferred */ size_t buflen; /* Buffer length used for transfer */ sem_t iocsem; /* Semaphore used to wait for transfer completion */ #ifdef CONFIG_USBHOST_ASYNCH usbhost_asynch_t callback; /* Transfer complete callback */ FAR void *arg; /* Argument that accompanies the callback */ #endif struct xhci_ring_s td; /* TD ring for this endpoint */ uint8_t slot; /* Slot where this EP resides */ /* These fields are used in the split-transaction protocol. */ uint8_t hubaddr; /* USB device address of the high-speed hub below * which a full/low-speed device is attached. */ uint8_t hubport; /* The port on the above high-speed hub. */ }; /* This structure retains the state of one root hub port */ struct xhci_rhport_s { /* Common device fields. This must be the first thing defined in the * structure so that it is possible to simply cast from struct usbhost_s * to struct xhci_rhport_s. */ struct usbhost_driver_s drvr; /* Root hub port status */ bool connected; /* Connected to device */ int8_t slot; /* Slot ID associated with this port */ struct xhci_epinfo_s ep0; /* EP0 endpoint info */ struct usbhost_roothubport_s hport; /* This is the hub port description understood * by class drivers */ FAR struct usbhost_xhci_s *priv; /* Reference to xHCI instance */ FAR struct xhci_dev_s *dev; /* Device reference */ }; /* USB Devices xhci data */ struct xhci_dev_s { uint8_t state; /* Slot stat */ uint8_t slot; /* Slot ID associated with this device */ FAR struct xhci_dev_ctx_s *ctx; /* Output Device Context. Managed by xHC */ FAR struct xhci_input_dev_ctx_s *input; /* Input Device Context. Input to xHC */ FAR struct xhci_rhport_s *rhport; /* Root Hub Port associated with this device */ /* Reference to allocated endpoints */ FAR struct xhci_epinfo_s *epinfo[XHCI_MAX_ENDPOINTS]; }; /* This structure contains the internal, private state of the xhci driver */ struct usbhost_xhci_s { #ifdef CONFIG_USBHOST_HUB FAR struct usbhost_hubport_s *hport; /* Used to pass external hub port events */ #endif struct usbhost_devaddr_s devgen; /* Address generation data */ bool pscwait; /* TRUE: Thread is waiting for port status change event */ sem_t pscsem; /* Semaphore to wait for port status change events */ mutex_t lock; /* Support mutually exclusive access */ spinlock_t spinlock; /* xHCI parameters */ uint8_t no_ports; /* Number of USB Ports */ uint8_t no_slots; /* Maximum number of Device Slots (one per USB device) */ uint8_t no_scratch; /* Number of scratch buffers */ uint8_t no_erst; /* Event Ring Segment Table size */ /* xHCI data */ FAR struct xhci_rhport_s *rhport; /* Root hub ports */ FAR struct xhci_dev_s *devs; /* USB device xHC data. One entry per * one supported USB device. */ /* Allocated buffers for controller */ FAR uint64_t *pg_ctx; /* Device Context (no_slots + 1 elements). * Slot 0 reserved for Scratchpad Buffer Array */ FAR uint64_t *pg_sb; /* Scratchpad Buffer Array (no_scratch elements) */ FAR struct xhci_event_ring_s *pg_erst; /* Event Ring Segment Table */ /* Event ring handling */ struct xhci_ring_s evnt; /* Event ring handler */ /* Command ring handling */ sem_t cmdsem; /* Command done semaphore */ struct xhci_trb_s cmdres; /* Command result */ struct xhci_ring_s cmd; /* Command ring handler */ /* PCI data */ FAR struct pci_device_s *pcidev; /* PCI device reference */ int irq; /* IRQ number used by the device */ uint32_t pending; /* IRQ pending status */ struct work_s work; /* IRQ work */ struct work_s pscwork; /* Port status change work */ uint64_t base; /* xHCI base address */ uint64_t capa_base; /* Capability base */ uint64_t oper_base; /* Operational base */ uint64_t runt_base; /* Runtime base */ uint64_t door_base; /* Doorbell base */ }; /* xHCI connection monitoring */ struct usbhost_conn_xhci_s { struct usbhost_connection_s conn; /* Connection monitoring */ FAR struct usbhost_xhci_s *priv; /* Reference to xHCI instance */ int pid; /* Waiter thread PID */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Helpers ******************************************************************/ static uint32_t xhci_capa_getreg(FAR struct usbhost_xhci_s *priv, unsigned int offset); static uint8_t xhci_capa_getreg_1b(FAR struct usbhost_xhci_s *priv, unsigned int offset); static void xhci_capa_putreg_1b(FAR struct usbhost_xhci_s *priv, unsigned int offset, uint8_t value); static uint32_t xhci_oper_getreg(FAR struct usbhost_xhci_s *priv, unsigned int offset); static void xhci_oper_putreg(FAR struct usbhost_xhci_s *priv, unsigned int offset, uint32_t value); static void xhci_oper_putreg_8b(FAR struct usbhost_xhci_s *priv, unsigned int offset, uint64_t value); static uint32_t xhci_runt_getreg(FAR struct usbhost_xhci_s *priv, unsigned int offset); static void xhci_runt_putreg(FAR struct usbhost_xhci_s *priv, unsigned int offset, uint32_t value); static void xhci_runt_putreg_8b(FAR struct usbhost_xhci_s *priv, unsigned int offset, uint64_t value); static void xhci_door_putreg(FAR struct usbhost_xhci_s *priv, unsigned int offset, uint32_t value); /* Byte stream access helper functions **************************************/ static inline uint16_t xhci_getle16(FAR const uint8_t *val); /* Debug ********************************************************************/ #ifdef CONFIG_DEBUG_USB_INFO static void xhci_dump_capa_reg(FAR struct usbhost_xhci_s *priv, FAR const char *msg, unsigned int offset); static void xhci_dump_oper_reg(FAR struct usbhost_xhci_s *priv, FAR const char *msg, unsigned int offset); static void xhci_dump_runt_reg(FAR struct usbhost_xhci_s *priv, FAR const char *msg, unsigned int offset); static void xhci_dump_mem(FAR struct usbhost_xhci_s *priv, FAR const char *msg); #endif /* Ring management **********************************************************/ static int xhci_ring_init(FAR struct xhci_ring_s *ring, size_t len); static void xhci_ring_deinit(FAR struct xhci_ring_s *ring); static void xhci_ring_reset(FAR struct xhci_ring_s *ring, bool swap_ccs); static void xhci_add_trb(FAR struct usbhost_xhci_s *priv, FAR struct xhci_ring_s *ring, FAR struct xhci_trb_s *trb, int len); /* xHCI operations **********************************************************/ static int xhci_bios_wait(FAR struct usbhost_xhci_s *priv); static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv); static int xhci_ctrl_halt(FAR struct usbhost_xhci_s *priv); static int xhci_ctrl_reset(FAR struct usbhost_xhci_s *priv); /* Port management **********************************************************/ static void xhci_probe_ports(FAR struct usbhost_xhci_s *priv); static int xhci_port_enable(FAR struct usbhost_xhci_s *priv, FAR struct usbhost_hubport_s *hport); /* Slot management **********************************************************/ static void xhci_dcbaa_set(FAR struct usbhost_xhci_s *priv, uint8_t index, uintptr_t ctx); static void xhci_ep_configure(FAR struct usbhost_xhci_s *priv, FAR struct xhci_ep_ctx_s *ctx, uint8_t type, uint16_t maxpkt, uint8_t maxburst, uint64_t tr_dp, uint8_t mult, uint8_t interval); static int xhci_address_set(FAR struct usbhost_xhci_s *priv, FAR struct xhci_rhport_s *rhport, bool setaddr); static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, FAR struct xhci_dev_s *dev); static int xhci_device_init(FAR struct usbhost_xhci_s *priv, FAR struct xhci_rhport_s *rhport); static int xhci_device_deinit(FAR struct usbhost_xhci_s *priv, FAR struct xhci_rhport_s *rhport); static inline uint8_t xhci_epno_get(FAR struct xhci_epinfo_s *epinfo); static void xhci_context_ctrl(FAR struct usbhost_xhci_s *priv, FAR struct xhci_dev_s *dev, uint32_t drop, uint32_t add); /* Command handling *********************************************************/ static int xhci_command(FAR struct usbhost_xhci_s *priv, FAR struct xhci_trb_s *trb, uint16_t timeout_ms); static int xhci_cmd_sloten(FAR struct usbhost_xhci_s *priv, FAR uint8_t *slot); static int xhci_cmd_slotdis(FAR struct usbhost_xhci_s *priv, uint8_t slot); static int xhci_cmd_setaddr(FAR struct usbhost_xhci_s *priv, uint8_t slot, uint64_t ctx, bool bsr); static int xhci_cmd_cfgep(FAR struct usbhost_xhci_s *priv, uint8_t slot, uint64_t ctx, bool deconfig); static int xhci_cmd_stopep(FAR struct usbhost_xhci_s *priv, uint8_t slot, uint8_t ep, bool suspend); static int xhci_cmd_evalctx(FAR struct usbhost_xhci_s *priv, uint8_t slot, uint64_t ctx); /* Transfer handling ********************************************************/ static void xhci_ep_doorbell(FAR struct usbhost_xhci_s *priv, FAR struct xhci_epinfo_s *epinfo); static int xhci_ioc_setup(FAR struct xhci_rhport_s *rhport, FAR struct xhci_epinfo_s *epinfo, size_t buflen); static int xhci_ioc_wait(FAR struct xhci_epinfo_s *epinfo); #ifdef CONFIG_USBHOST_ASYNCH static inline int xhci_ioc_async_setup(FAR struct xhci_rhport_s *rhport, FAR struct xhci_epinfo_s *epinfo, usbhost_asynch_t callback, FAR void *arg); static void xhci_asynch_completion(FAR struct xhci_epinfo_s *epinfo); #endif static int xhci_control_setup(FAR struct xhci_rhport_s *rhport, FAR struct xhci_epinfo_s *epinfo, FAR const struct usb_ctrlreq_s *req, FAR uint8_t *buffer, size_t buflen); static int xhci_normal_setup(FAR struct xhci_rhport_s *rhport, FAR struct xhci_epinfo_s *epinfo, FAR uint8_t *buffer, size_t buflen); #ifndef CONFIG_USBHOST_ISOC_DISABLE static int xhci_isoc_setup(FAR struct xhci_rhport_s *rhport, FAR struct xhci_epinfo_s *epinfo, FAR uint8_t *buffer, size_t buflen); #endif static ssize_t xhci_transfer_wait(FAR struct usbhost_xhci_s *priv, FAR struct xhci_epinfo_s *epinfo); /* Interrupt handling *******************************************************/ static void xhci_portsc_work(FAR void *arg); static void xhci_transfer_complete(FAR struct usbhost_xhci_s *priv, FAR struct xhci_trb_s *evt); static void xhci_event_complete(FAR struct usbhost_xhci_s *priv, FAR struct xhci_trb_s *evt); static int xhci_events_poll(FAR struct usbhost_xhci_s *priv); static void xhci_interrupt_work(FAR void *arg); static int xhci_interrupt(int irq, FAR void *context, FAR void *arg); /* USB host controller operations *******************************************/ static int xhci_wait(FAR struct usbhost_connection_s *conn, FAR struct usbhost_hubport_s **hport); static int xhci_rh_enumerate(FAR struct usbhost_connection_s *conn, FAR struct usbhost_hubport_s *hport); static int xhci_enumerate(FAR struct usbhost_connection_s *conn, FAR struct usbhost_hubport_s *hport); static int xhci_ep0configure(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, uint8_t funcaddr, uint8_t speed, uint16_t maxpacketsize); static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, FAR const struct usbhost_epdesc_s *epdesc, FAR usbhost_ep_t *ep); static int xhci_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep); static int xhci_alloc(FAR struct usbhost_driver_s *drvr, FAR uint8_t **buffer, FAR size_t *maxlen); static int xhci_free(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer); static int xhci_ioalloc(FAR struct usbhost_driver_s *drvr, FAR uint8_t **buffer, size_t buflen); static int xhci_iofree(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer); static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, FAR const struct usb_ctrlreq_s *req, FAR uint8_t *buffer); static int xhci_ctrlin(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, FAR const struct usb_ctrlreq_s *req, FAR uint8_t *buffer); static int xhci_ctrlout(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, FAR const struct usb_ctrlreq_s *req, FAR const uint8_t *buffer); static ssize_t xhci_transfer(FAR struct usbhost_driver_s *drvr, FAR usbhost_ep_t ep, uint8_t *buffer, size_t buflen); #ifdef CONFIG_USBHOST_ASYNCH static int xhci_asynch(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, FAR uint8_t *buffer, size_t buflen, usbhost_asynch_t callback, void *arg); #endif static int xhci_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep); #ifdef CONFIG_USBHOST_HUB static int xhci_connect(FAR struct usbhost_driver_s *drvr, FAR struct usbhost_hubport_s *hport, bool connected); #endif static void xhci_disconnect(FAR struct usbhost_driver_s *drvr, FAR struct usbhost_hubport_s *hport); /* Initialization ***********************************************************/ static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv); static int xhci_irq_initialize(FAR struct usbhost_xhci_s *priv); 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 ****************************************************************************/ /**************************************************************************** * Name: xhci_capa_getreg * * Description: * Get register (USB Legacy Support Capability) * ****************************************************************************/ static uint32_t xhci_capa_getreg(FAR struct usbhost_xhci_s *priv, unsigned int offset) { uintptr_t addr = priv->capa_base + offset; return *((FAR volatile uint32_t *)addr); } /**************************************************************************** * Name: xhci_capa_getreg_1b * * Description: * Get 1B register (USB Legacy Support Capability) * ****************************************************************************/ static uint8_t xhci_capa_getreg_1b(FAR struct usbhost_xhci_s *priv, unsigned int offset) { uintptr_t addr = priv->capa_base + offset; return *((FAR volatile uint8_t *)addr); } /**************************************************************************** * Name: xhci_capa_putreg_1b * * Description: * Put 1B register (USB Legacy Support Capability) * ****************************************************************************/ static void xhci_capa_putreg_1b(FAR struct usbhost_xhci_s *priv, unsigned int offset, uint8_t value) { uintptr_t addr = priv->capa_base + offset; *((FAR volatile uint8_t *)addr) = value; } /**************************************************************************** * Name: xhci_oper_getreg * * Description: * Get register (Host Controller Operational Registers) * ****************************************************************************/ static uint32_t xhci_oper_getreg(FAR struct usbhost_xhci_s *priv, unsigned int offset) { uintptr_t addr = priv->oper_base + offset; return *((FAR volatile uint32_t *)addr); } /**************************************************************************** * Name: xhci_oper_putreg * * Description: * Put register (Host Controller Operational Registers) * ****************************************************************************/ static void xhci_oper_putreg(FAR struct usbhost_xhci_s *priv, unsigned int offset, uint32_t value) { uintptr_t addr = priv->oper_base + offset; *((FAR volatile uint32_t *)addr) = value; } /**************************************************************************** * Name: xhci_oper_putreg_8b * * Description: * Put register (Host Controller Operational Registers) * ****************************************************************************/ static void xhci_oper_putreg_8b(FAR struct usbhost_xhci_s *priv, unsigned int offset, uint64_t value) { uintptr_t addr = priv->oper_base + offset; *((FAR volatile uint64_t *)addr) = value; } /**************************************************************************** * Name: xhci_runt_getreg * * Description: * Get register (Host Controller Runtime Registers) * ****************************************************************************/ static uint32_t xhci_runt_getreg(FAR struct usbhost_xhci_s *priv, unsigned int offset) { uintptr_t addr = priv->runt_base + offset; return *((FAR volatile uint32_t *)addr); } /**************************************************************************** * Name: xhci_runt_putreg * * Description: * Put register (Host Controller Runtime Registers) * ****************************************************************************/ static void xhci_runt_putreg(FAR struct usbhost_xhci_s *priv, unsigned int offset, uint32_t value) { uintptr_t addr = priv->runt_base + offset; *((FAR volatile uint32_t *)addr) = value; } /**************************************************************************** * Name: xhci_runt_putreg_8b * * Description: * Put register (Host Controller Runtime Registers) * ****************************************************************************/ static void xhci_runt_putreg_8b(FAR struct usbhost_xhci_s *priv, unsigned int offset, uint64_t value) { uintptr_t addr = priv->runt_base + offset; *((FAR volatile uint64_t *)addr) = value; } /**************************************************************************** * Name: xhci_door_putreg * * Description: * Put register (Doorbell Registers) * ****************************************************************************/ static void xhci_door_putreg(FAR struct usbhost_xhci_s *priv, unsigned int offset, uint32_t value) { uintptr_t addr = priv->door_base + offset; *((FAR volatile uint32_t *)addr) = value; } #ifdef CONFIG_DEBUG_USB_INFO /**************************************************************************** * Name: xhci_dump_capa_reg ****************************************************************************/ 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)); } /**************************************************************************** * Name: xhci_dump_oper_reg ****************************************************************************/ 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)); } /**************************************************************************** * Name: xhci_dump_runt_reg ****************************************************************************/ 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)); } /**************************************************************************** * Name: xhci_dump_mem ****************************************************************************/ static void xhci_dump_mem(FAR struct usbhost_xhci_s *priv, FAR const char *msg) { int i; pciinfo("Dump xHCI registers: %s\n", msg); pciinfo("=== 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); xhci_dump_capa_reg(priv, "HCSPARAMS2 ", XHCI_HCSPARAMS2); xhci_dump_capa_reg(priv, "HCSPARAMS3 ", XHCI_HCSPARAMS3); xhci_dump_capa_reg(priv, "HCCPARAMS1 ", XHCI_HCCPARAMS1); xhci_dump_capa_reg(priv, "DBOFF ", XHCI_DBOFF); xhci_dump_capa_reg(priv, "RTSOFF ", XHCI_RTSOFF); xhci_dump_capa_reg(priv, "HCCPARAMS2 ", XHCI_HCCPARAMS2); pciinfo("=== 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); xhci_dump_oper_reg(priv, "DNCTRL ", XHCI_DNCTRL); xhci_dump_oper_reg(priv, "CRCR ", XHCI_CRCR); xhci_dump_oper_reg(priv, "DCBAAP ", XHCI_DCBAAP); xhci_dump_oper_reg(priv, "CONFIG ", XHCI_CONFIG); for (i = 0; i < priv->no_ports; i++) { pciinfo("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)); } /* Only one interrupter used */ pciinfo("=== 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)); xhci_dump_runt_reg(priv, "ERSTSZ(0) ", XHCI_ERSTSZ(0)); xhci_dump_runt_reg(priv, "ERSTBA(0) ", XHCI_ERSTBA(0)); xhci_dump_runt_reg(priv, "ERDP(0) ", XHCI_ERDP(0)); } #endif /**************************************************************************** * Name: xhci_getle16 * * Description: * Get a (possibly unaligned) 16-bit little endian value. * ****************************************************************************/ static inline uint16_t xhci_getle16(FAR const uint8_t *val) { #ifdef CONFIG_ENDIAN_BIG return (uint16_t)val[0] << 8 | (uint16_t)val[1]; #else return (uint16_t)val[1] << 8 | (uint16_t)val[0]; #endif } /**************************************************************************** * Name: xhci_ring_init * * Description: * Initialize xHCI ring handler. * * If ring buffer is already initialized, this function reset ring * to a initial state. * * Returned Value: * OK on success. * ****************************************************************************/ static int xhci_ring_init(FAR struct xhci_ring_s *ring, size_t len) { FAR struct xhci_trb_s *trb; if (!ring->ring) { /* Allocate ring data */ ring->ring = kmm_memalign(XHCI_BUF_ALIGN, sizeof(struct xhci_trb_s) * len); if (!ring->ring) { return -ENOMEM; } /* Store length */ ring->len = len; } /* Reset data in ring */ memset(ring->ring, 0, ring->len * sizeof(struct xhci_trb_s)); /* Fill Link TRB */ trb = &ring->ring[ring->len - 1]; trb->d0 = htole64(up_addrenv_va_to_pa(&ring->ring[0])); trb->d1 = 0; trb->d2 = 0; up_flush_dcache((uintptr_t)trb, (uintptr_t)(trb + 1)); /* Reset state */ ring->i = 0; ring->ccs = true; return OK; } /**************************************************************************** * Name: xhci_ring_deinit * * Description: * Initialize xHCI ring handler. * * Returned Value: * None * ****************************************************************************/ static void xhci_ring_deinit(FAR struct xhci_ring_s *ring) { /* Free ring memory */ kmm_free(ring->ring); } /**************************************************************************** * Name: xhci_ring_reset * * Description: * Reset xHCI ring handler. * * Returned Value: * None * ****************************************************************************/ static void xhci_ring_reset(FAR struct xhci_ring_s *ring, bool swap_ccs) { /* Reset pointer */ ring->i = 0; /* Swap CCS if requestede */ if (swap_ccs) { ring->ccs = !ring->ccs; } else { ring->ccs = true; } } /**************************************************************************** * Name: xhci_add_trb * * Description: * Reset TRB to a ring. * * Returned Value: * None * ****************************************************************************/ static void xhci_add_trb(FAR struct usbhost_xhci_s *priv, FAR struct xhci_ring_s *ring, FAR struct xhci_trb_s *trb, int len) { uint32_t d2; int i; for (i = 0; i < len; i++) { d2 = trb[i].d2; if (ring->ccs) { d2 |= XHCI_TRB_D2_C; } else { d2 &= ~XHCI_TRB_D2_C; } /* Make sure the cycle bit has the correct value */ DEBUGASSERT((ring->ring[ring->i].d2 & XHCI_TRB_D2_C) != ring->ccs); /* Write TRB */ ring->ring[ring->i].d0 = htole64(trb[i].d0); ring->ring[ring->i].d1 = htole32(trb[i].d1); ring->ring[ring->i].d2 = htole32(d2); /* Next TD */ ring->i++; /* Handle end of the command ring */ if (ring->i >= ring->len - 1) { /* Make sure the cycle bit has the correct value */ DEBUGASSERT((ring->ring[0].d2 & XHCI_TRB_D2_C) == ring->ccs); if (ring->ccs) { d2 = XHCI_TRB_D2_C | XHCI_TRB_D2_TC | XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_LINK); } else { d2 = XHCI_TRB_D2_TC | XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_LINK); } /* Other parameters are already correct for this TRB */ ring->ring[ring->i].d2 = htole32(d2); /* Update CCS */ xhci_ring_reset(ring, true); } } /* Flush ring */ up_flush_dcache((uintptr_t)ring->ring, (uintptr_t)(ring->ring + ring->len)); } /**************************************************************************** * Name: xhci_bios_wait * * Description: * Wait for BIOS to give up the controller lock * * Returned Value: * Zero on success; a negated errno value on failure. * ****************************************************************************/ static int xhci_bios_wait(FAR struct usbhost_xhci_s *priv) { uint32_t cstart; uint32_t ecp; uint32_t eec; uint8_t sem; uint8_t timeout; int ret = OK; /* Get Extended Capability Pointer */ cstart = XHCI_HCCPARAMS1_XECP(xhci_capa_getreg(priv, XHCI_HCCPARAMS1)); /* Find USBLEGSUP - if present, we have to acquire for BIOS semaphore */ eec = -1; ecp = (cstart << 2); while (1) { if (ecp == 0 || XHCI_USBLEGSUP_NEXT(eec) == 0) { break; } eec = xhci_capa_getreg(priv, ecp); if (XHCI_USBLEGSUP_ID(eec) == XHCI_ID_USBLEGSUP) { /* We have to wait for semaphore */ ret = -EAGAIN; /* Get BIOS semaphore */ sem = xhci_capa_getreg_1b(priv, ecp + XHCI_USBLEGSUP_BIOS_SEM); if (sem == 0) { ret = OK; break; } /* Get semaphore request */ xhci_capa_putreg_1b(priv, ecp + XHCI_USBLEGSUP_OS_SEM, 1); /* Wait for semaphore released from BIOS */ for (timeout = 0; timeout < 100; timeout++) { sem = xhci_capa_getreg_1b(priv, ecp + XHCI_USBLEGSUP_BIOS_SEM); if (sem == 0) { ret = OK; break; } up_mdelay(100); } } /* Next cap */ ecp += (XHCI_USBLEGSUP_NEXT(eec) << 2); } return ret; } /**************************************************************************** * Name: xhci_ctrl_start * * Description: * Start controller. * * According to "4.2 Host Controller Initialization". * * Returned Value: * Zero on success; a negated errno value on failure. * ****************************************************************************/ static int xhci_ctrl_start(FAR struct usbhost_xhci_s *priv) { FAR struct xhci_event_ring_s *evnt; uint32_t regval; int ret; int i; pciinfo("Start controller\n"); /* Reset controller before writing any Operational or Runtime registers */ ret = xhci_ctrl_reset(priv); if (ret < 0) { usbhost_trace1(XHCI_TRACE1_RESET_FAILED, 0); return ret; } /* TODO: clear interrupts and disable all device notifications */ /* Max Device Slots Enabled */ xhci_oper_putreg(priv, XHCI_CONFIG, priv->no_slots); /* Slot 0 in Device Context is reserved for Scratchpad Buffer Array */ priv->pg_ctx[0] = htole64(up_addrenv_va_to_pa(priv->pg_sb)); /* Device Context Base Address Array Pointer */ xhci_oper_putreg_8b(priv, XHCI_DCBAAP, up_addrenv_va_to_pa(priv->pg_ctx)); /* Event Ring Segment Table Size */ xhci_runt_putreg(priv, XHCI_ERSTSZ(0), priv->no_erst); /* Initialize command ring state and event ring state */ ret = xhci_ring_init(&priv->cmd, XHCI_CMD_MAX); if (ret < 0) { pcierr("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"); return ret; } /* Configure Event Ring */ evnt = (struct xhci_event_ring_s *)priv->pg_erst; evnt->base = htole64(up_addrenv_va_to_pa(priv->evnt.ring)); evnt->size = XHCI_EVENT_MAX; evnt->res = 0; /* Flush all memory before write to ERDP so xhci sees correct data */ up_flush_dcache_all(); xhci_runt_putreg_8b(priv, XHCI_ERDP(0), up_addrenv_va_to_pa(priv->evnt.ring)); /* Write ERSTBA with ERST(0).BaseAddress. * * This must be done after ERST[0] initialization and after write to * ERSTSZ. When the ERSTBA register is written, the Event Ring State * Machine is set to the Start state. * * For details look at "4.9.4 Event Ring Management" */ xhci_runt_putreg_8b(priv, XHCI_ERSTBA(0), up_addrenv_va_to_pa(priv->pg_erst)); /* Last item in the command ring points to the beginning of the ring */ priv->cmd.ring[XHCI_CMD_MAX - 1].d0 = htole64( up_addrenv_va_to_pa(priv->cmd.ring)); /* Configure the Command Ring */ xhci_oper_putreg_8b(priv, XHCI_CRCR, up_addrenv_va_to_pa(priv->cmd.ring) | XHCI_CRCR_RCS); /* Enable interrupts */ regval = xhci_runt_getreg(priv, XHCI_IMAN(0)); regval |= XHCI_IMAN_IE; xhci_runt_putreg(priv, XHCI_IMAN(0), regval); /* Flush all memory once again */ up_flush_dcache_all(); /* Turn the host controller ON, enable interrupts and system errors */ xhci_oper_putreg(priv, XHCI_USBCMD, XHCI_USBCMD_RS | XHCI_USBCMD_INTE | XHCI_USBCMD_HSEE); /* Wait for controller started */ ret = -EAGAIN; for (i = 0; i < 10; i++) { up_mdelay(100); if (!(xhci_oper_getreg(priv, XHCI_USBSTS) & XHCI_USBSTS_HCH)) { ret = OK; break; } } /* Check for timeout */ if (ret != OK) { pcierr("Can't start controller!"); return ret; } /* Poll all pending events */ xhci_events_poll(priv); return OK; } /**************************************************************************** * Name: xhci_ctrl_halt * * Description: * Halt controller. * * Returned Value: * Zero on success; a negated errno value on failure. * ****************************************************************************/ static int xhci_ctrl_halt(FAR struct usbhost_xhci_s *priv) { int ret = -EAGAIN; int i; /* Halt controller */ xhci_oper_putreg(priv, XHCI_USBCMD, 0); /* Wait for controller halted */ for (i = 0; i < 10; i++) { up_mdelay(100); if (xhci_oper_getreg(priv, XHCI_USBSTS) & XHCI_USBSTS_HCH) { ret = OK; break; } } return ret; } /**************************************************************************** * Name: xhci_ctrl_reset * * Description: * Reset controller. * * Returned Value: * Zero on success; a negated errno value on failure. * ****************************************************************************/ static int xhci_ctrl_reset(FAR struct usbhost_xhci_s *priv) { int ret = -EAGAIN; int i; /* Halt controller */ xhci_oper_putreg(priv, XHCI_USBCMD, XHCI_USBCMD_HCRST); /* Wait for controller halted */ for (i = 0; i < 10; i++) { up_mdelay(100); if (!(xhci_oper_getreg(priv, XHCI_USBSTS) & XHCI_USBSTS_CNR)) { ret = OK; break; } } return ret; } /**************************************************************************** * Name: xhci_probe_ports * * Description: * Initial ports probe. * ****************************************************************************/ static void xhci_probe_ports(FAR struct usbhost_xhci_s *priv) { uint32_t portsc; int i; for (i = 0; i < priv->no_ports; i++) { portsc = xhci_oper_getreg(priv, XHCI_PORTSC(i)); priv->rhport[i].connected = ((portsc & XHCI_PORTSC_CCS) != 0); /* Clear status change */ xhci_oper_putreg(priv, XHCI_PORTSC(i), portsc); } } /**************************************************************************** * Name: xhci_port_enable * * Description: * Set port to the Enable state. * ****************************************************************************/ static int xhci_port_enable(FAR struct usbhost_xhci_s *priv, FAR struct usbhost_hubport_s *hport) { uint32_t retries; uint32_t regval; uint8_t speed; int rhpndx; DEBUGASSERT(hport != NULL); rhpndx = hport->port; regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); /* A USB3 protocol port attempts to automatically advance to the * Enabled state for port as part of the attach process. */ if (!(regval & XHCI_PORTSC_PED)) { /* Reset the port */ regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); regval |= XHCI_PORTSC_PR; xhci_oper_putreg(priv, XHCI_PORTSC(rhpndx), regval); /* REVISIT: we get Port Status Change Event here */ /* Wait for Enabled state for port */ retries = 10; while (!(xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)) & XHCI_PORTSC_PED) && retries > 0) { retries--; up_mdelay(100); } if (retries == 0) { return -ETIMEDOUT; } } /* Get port status */ regval = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); /* Get port speed */ speed = XHCI_PORTSC_PS(regval); switch (speed) { case XHCI_PORTSC_PS_FULL: { hport->speed = USB_SPEED_FULL; break; } case XHCI_PORTSC_PS_LOW: { hport->speed = USB_SPEED_LOW; break; } case XHCI_PORTSC_PS_HIGH: { hport->speed = USB_SPEED_HIGH; break; } case XHCI_PORTSC_PS_SUPPER11: { hport->speed = USB_SPEED_SUPER; break; } case XHCI_PORTSC_PS_SUPPER21: case XHCI_PORTSC_PS_SUPPER12: case XHCI_PORTSC_PS_SUPPER22: { hport->speed = USB_SPEED_SUPER_PLUS; break; } default: { pcierr("speed = 0x%x\n", speed); hport->speed = USB_SPEED_UNKNOWN; return -EINVAL; } } return OK; } /**************************************************************************** * Name: xhci_dcbaa_set * * Description: * Set entry in the Device Context Base Address Array, which should point * to the Output Device Context data structure. * ****************************************************************************/ static void xhci_dcbaa_set(FAR struct usbhost_xhci_s *priv, uint8_t index, uintptr_t ctx) { /* NOTE: context must be physical address! */ priv->pg_ctx[index] = htole64(ctx); /* Flush context */ up_flush_dcache((uintptr_t)priv->pg_ctx, (uintptr_t)(priv->pg_ctx + priv->no_slots + 1)); } /**************************************************************************** * Name: xhci_ep_configure * * Description: * Configure endpoint context. * * Reference: * - 4.8.2 Endpoint Context Initialization * - 6.2.3 Endpoint Context * ****************************************************************************/ static void xhci_ep_configure(FAR struct usbhost_xhci_s *priv, FAR struct xhci_ep_ctx_s *ctx, uint8_t type, uint16_t maxpkt, uint8_t maxburst, uint64_t tr_dp, uint8_t mult, uint8_t interval) { uint32_t ctx0 = 0; uint32_t ctx1 = 0; uint64_t ctx2 = 0; /* Set type */ ctx1 |= XHCI_EP_CTX1_EPTYPE(type); /* Set max packet size */ ctx1 |= XHCI_EP_CTX1_MAXPKT(maxpkt); /* Set max burst size */ ctx1 |= XHCI_EP_CTX1_MAXBRST(maxburst); /* Set TR Dequeue Pointer. * NOTE: must be physical address aligned to 16-byte. */ DEBUGASSERT(tr_dp != 0 && tr_dp % 16 == 0); ctx2 = tr_dp; /* Set DCS. * Should be set to 1 only if no stream (USB3.0 specific). */ ctx2 |= XHCI_EP_CTX2_DCS; /* Set interval */ ctx0 |= XHCI_EP_CTX0_INTERVAL(interval); /* Set max primary streams. * Set to zero for now (USB3.0 specific) */ ctx0 |= XHCI_EP_CTX0_MAXPSTR(0); /* Set mult */ ctx0 |= XHCI_EP_CTX0_MULT(mult); /* Set error count to 3 if this is not ISOCH endpoint */ if (type != XHCI_EPTYPE_ISO_OUT && type != XHCI_EPTYPE_ISO_IN) { ctx1 |= XHCI_EP_CTX1_CERR(3); } /* Write context */ ctx->ctx0 = htole32(ctx0); ctx->ctx1 = htole32(ctx1); ctx->ctx2 = htole64(ctx2); /* Flush context */ up_flush_dcache((uintptr_t)ctx, (uintptr_t)ctx + sizeof(struct xhci_ep_ctx_s)); } /**************************************************************************** * Name: xhci_address_set * * Description: * Set address request. * * If setaddr is true, then xHC issue a SET_ADDRESS request. * ****************************************************************************/ static int xhci_address_set(FAR struct usbhost_xhci_s *priv, FAR struct xhci_rhport_s *rhport, bool setaddr) { FAR struct xhci_dev_s *dev; uint64_t ctx; dev = rhport->dev; ctx = up_addrenv_va_to_pa(dev->input); return xhci_cmd_setaddr(priv, rhport->slot, ctx, !setaddr); } /**************************************************************************** * Name: xhci_slot_init * * Description: * Initialize Device Slot data. * * Assumption: * 1. All slot resources already allocated. * 2. Port is in Enabled state. * * Reference: * - 4.3.3. Device Slot Initialization * ****************************************************************************/ static int xhci_slot_init(FAR struct usbhost_xhci_s *priv, FAR struct xhci_dev_s *dev) { uint32_t regval; uint16_t maxpkt; uintptr_t drdp; /* Step 1. The Input Context data structure already allocated. * Initialize all fields to 0. */ memset(dev->input, 0, sizeof(struct xhci_input_dev_ctx_s)); /* Step 2. Initialize the Input Control Context by setting the A0 and * A1 flags to 1 (Slot flag and EP0 flag). */ regval = XHCI_IN_CTX1_A(XHCI_SLOT_FLAG) | XHCI_IN_CTX1_A(XHCI_EP0_FLAG); xhci_context_ctrl(priv, dev, 0, regval); /* Step 3. Initialize the Input Slot Context */ regval = XHCI_ST_CTX0_CTXENT_SET(1); #ifdef CONFIG_USBHOST_HUB /* TODO: * 1. Activate the transaction translator if required * 2. Configure hub bit in slot context if hub * 3. configure route string */ # warning missing logic #endif dev->input->slot.ctx[0] = htole32(regval); /* Configure Root Hub Port Number (starts from 1) */ regval = XHCI_ST_CTX1_RHPN_SET(RHPNDX(dev->rhport) + 1); /* TODO: configure number of ports */ regval |= XHCI_ST_CTX1_PORTS_SET(0); dev->input->slot.ctx[1] = htole32(regval); /* Step 4. the Transfer Ring for the Default Control Endpoint is already * allocated. */ drdp = up_addrenv_va_to_pa(dev->rhport->ep0.td.ring); /* Step 5. Initialize the Input default control Endpoint 0 Context */ DEBUGASSERT(dev->rhport != NULL); if (dev->rhport->hport.hport.speed == USB_SPEED_HIGH) { /* For high-speed, we must use 64 bytes */ maxpkt = 64; } else { /* Eight will work for both low- and full-speed */ maxpkt = 8; } DEBUGASSERT(drdp != 0); xhci_ep_configure(priv, &dev->input->ep[0], XHCI_EPTYPE_CTRL, maxpkt, 0, drdp, 0, 0); /* Step 6. The output Device Context data structure already allocated. * Initialize all fields to 0. */ memset(dev->ctx, 0, sizeof(struct xhci_dev_ctx_s)); /* Flush Device input context */ up_flush_dcache((uintptr_t)dev->input, (uintptr_t)dev->input + sizeof(struct xhci_input_dev_ctx_s)); /* Step 7. Load the appropriate (Device Slot ID) entry in the Device * Context Base Address Array with a pointer to the Output Device * Context data structure */ xhci_dcbaa_set(priv, dev->slot, up_addrenv_va_to_pa(dev->ctx)); return OK; } /**************************************************************************** * Name: xhci_device_init * * Description: * Initialize Device. * * Assumption: * 1. All device resources already allocated. * 2. Port is in Enabled state. * * Reference: * - 4.3: USB Device Initialization * ****************************************************************************/ static int xhci_device_init(FAR struct usbhost_xhci_s *priv, FAR struct xhci_rhport_s *rhport) { FAR struct xhci_dev_s *dev; uint8_t slot; int ret; /* We enter this function after steps 1-3 from "USB Device Initialization" * are done. */ /* Step 4: Get Device Slot. * * Reference: * - 4.3.2: Device Slot Assignment */ ret = xhci_cmd_sloten(priv, &slot); if (ret < 0 || slot > priv->no_slots) { /* Something goes wrong ! */ usbhost_vtrace1(XHCI_TRACE1_SLOTEN_FAILED, ret); return ret; } /* Slot ID is an index to the identify Device data */ rhport->dev = &priv->devs[slot - 1]; rhport->slot = slot; dev = rhport->dev; /* Slot has been allocated to software and is now in Enabled state */ dev->state = XHCI_SLOT_ENABLED; /* Step 5: Initialize the data structures associated with the slot. * All data structured are already allocated. */ ret = xhci_ring_init(&rhport->ep0.td, XHCI_TD_MAX); if (ret < 0) { pcierr("ep0 ring init failed\n"); return ret; } rhport->ep0.slot = slot; dev->rhport = rhport; dev->slot = slot; dev->epinfo[0] = &rhport->ep0; ret = xhci_slot_init(priv, dev); if (ret < 0) { return ret; } /* Step 6: Assign and address to the device and enable its Default * Control Endpoint. * * NOTE: we don't send SET_ADDRESS request here. * This is done in xhci_ctrlin() and controlled by NuttX USB Host * stack. */ ret = xhci_address_set(priv, rhport, false); if (ret < 0) { pcierr("failed to set address %d\n", ret); return ret; } /* Steps 7-12 don't belong here! */ return OK; } /**************************************************************************** * Name: xhci_device_deinit * * Description: * Free Device. * * Reference: * - 4.3: USB Device Initialization * ****************************************************************************/ static int xhci_device_deinit(FAR struct usbhost_xhci_s *priv, FAR struct xhci_rhport_s *rhport) { uint8_t slot = rhport->slot; int ret; /* Disable Slot */ ret = xhci_cmd_slotdis(priv, slot); if (ret < 0) { pcierr("xhci_cmd_slotdis failed %d\n", ret); } /* Clear DCBAA entry for this slot */ xhci_dcbaa_set(priv, slot, 0); /* Clean up device data, but don't touch allocated memory! */ rhport->dev->state = XHCI_SLOT_DISABLED; memset(rhport->dev->ctx, 0, sizeof(struct xhci_dev_ctx_s)); memset(rhport->dev->input, 0, sizeof(struct xhci_input_dev_ctx_s)); /* Remove reference to a device slot */ rhport->dev = NULL; return OK; } /**************************************************************************** * Name: xhci_epno_get * * Description: * Get EP index for a given endpoint. * * Returns Device Context Index (DCI). * ****************************************************************************/ static inline uint8_t xhci_epno_get(FAR struct xhci_epinfo_s *epinfo) { DEBUGASSERT(epinfo); if (epinfo->epno == 0) { return 1; } if (epinfo->dirin) { return epinfo->epno * 2 + 1; } else { return epinfo->epno * 2; } } /**************************************************************************** * Name: xhci_context_ctrl * * Description: * Configure Input Control Context, which defines which Device Context * data structures are affected by a command. * * Assumption: * Input Context must be flushed by caller. * ****************************************************************************/ static void xhci_context_ctrl(FAR struct usbhost_xhci_s *priv, FAR struct xhci_dev_s *dev, uint32_t drop, uint32_t add) { int i; dev->input->input.ctx[0] = htole32(drop); dev->input->input.ctx[1] = htole32(add); /* Update Context Entries in Slot */ for (i = 31; i != 1; i--) { if (add & (1 << i)) { break; } } dev->input->slot.ctx[0] &= ~XHCI_ST_CTX0_CTXENT_MASK; dev->input->slot.ctx[0] |= XHCI_ST_CTX0_CTXENT_SET(i); } /**************************************************************************** * Name: xhci_command * * Description: * Issue a xHCI command. * * NOTE: * trb data in host specific byte order. This function converts it * to a correct order * ****************************************************************************/ static int xhci_command(FAR struct usbhost_xhci_s *priv, FAR struct xhci_trb_s *trb, uint16_t timeout_ms) { int ret; /* Lock bus */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { return ret; } /* Add command to ring */ xhci_add_trb(priv, &priv->cmd, trb, 1); /* Ringing the Host Controller Doorbell */ xhci_door_putreg(priv, XHCI_DOORBEL(0), 0); /* Wait for Command Completion Event */ ret = nxsem_tickwait_uninterruptible(&priv->cmdsem, MSEC2TICK(timeout_ms)); if (ret < 0) { /* Check for missed interrupts */ xhci_events_poll(priv); } /* Return command results */ trb->d0 = priv->cmdres.d0; trb->d1 = priv->cmdres.d1; trb->d2 = priv->cmdres.d2; if (XHCI_TRB_D1_CC_GET(trb->d1) != XHCI_TRB_CC_SUCCESS) { pcierr("event CC = %d\n", XHCI_TRB_D1_CC_GET(trb->d1)); ret = -EIO; } /* Clean response */ priv->cmdres.d0 = 0; priv->cmdres.d1 = 0; priv->cmdres.d2 = 0; /* Unlock bus */ nxmutex_unlock(&priv->lock); return ret; } /**************************************************************************** * Name: xhci_cmd_sloten * * Description: * Enable Slot Command. * ****************************************************************************/ static int xhci_cmd_sloten(FAR struct usbhost_xhci_s *priv, FAR uint8_t *slot) { struct xhci_trb_s trb; int ret; /* Host specific byte order. Conversion done by xhci_command() */ trb.d0 = 0; trb.d1 = 0; trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_EN_SLOT); ret = xhci_command(priv, &trb, 1000); if (ret < 0) { *slot = 0; return ret; } /* Return available slot */ *slot = XHCI_TRB_D2_SLOTID_GET(le32toh(trb.d2)); return OK; } /**************************************************************************** * Name: xhci_cmd_slotdis * * Description: * Disable Slot Command. * ****************************************************************************/ static int xhci_cmd_slotdis(FAR struct usbhost_xhci_s *priv, uint8_t slot) { struct xhci_trb_s trb; /* Host specific byte order. Conversion done by xhci_command() */ trb.d0 = 0; trb.d1 = 0; trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_DIS_SLOT) | XHCI_TRB_D2_SLOTID_SET(slot); return xhci_command(priv, &trb, 100); } /**************************************************************************** * Name: xhci_cmd_setaddr * * Description: * Address Device Command. * ****************************************************************************/ static int xhci_cmd_setaddr(FAR struct usbhost_xhci_s *priv, uint8_t slot, uint64_t ctx, bool bsr) { struct xhci_trb_s trb; /* Host specific byte order. Conversion done by xhci_command() */ trb.d0 = htole64(ctx); trb.d1 = 0; trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_ADDR_DEV) | XHCI_TRB_D2_SLOTID_SET(slot); if (bsr) { trb.d2 |= XHCI_TRB_D2_BSR; } return xhci_command(priv, &trb, 100); } /**************************************************************************** * Name: xhci_cmd_cfgep * * Description: * Configure EP Command. * ****************************************************************************/ static int xhci_cmd_cfgep(FAR struct usbhost_xhci_s *priv, uint8_t slot, uint64_t ctx, bool deconfig) { struct xhci_trb_s trb; /* Host specific byte order. Conversion done by xhci_command() */ trb.d0 = htole64(ctx); trb.d1 = 0; trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_CFG_EP) | XHCI_TRB_D2_SLOTID_SET(slot); if (deconfig) { trb.d2 |= XHCI_TRB_D2_DC; } return xhci_command(priv, &trb, 100); } /**************************************************************************** * Name: xhci_cmd_stopep * * Description: * Stop endpoint * ****************************************************************************/ static int xhci_cmd_stopep(FAR struct usbhost_xhci_s *priv, uint8_t slot, uint8_t ep, bool suspend) { struct xhci_trb_s trb; /* Host specific byte order. Conversion done by xhci_command() */ trb.d0 = 0; trb.d1 = 0; trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_STOP_EP) | XHCI_TRB_D2_SLOTID_SET(slot) | XHCI_TRB_D2_EP_SET(ep); if (suspend) { trb.d2 |= XHCI_TRB_D2_SP; } return xhci_command(priv, &trb, 100); } /**************************************************************************** * Name: xhci_cmd_evalctx * * Description: * Evaluate Context Command * ****************************************************************************/ static int xhci_cmd_evalctx(FAR struct usbhost_xhci_s *priv, uint8_t slot, uint64_t ctx) { struct xhci_trb_s trb; /* Host specific byte order. Conversion done by xhci_command() */ trb.d0 = htole64(ctx); trb.d1 = 0; trb.d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_EVAL_CTX) | XHCI_TRB_D2_SLOTID_SET(slot); return xhci_command(priv, &trb, 100); } /**************************************************************************** * Name: xhci_ep_doorbell * * Description: * Ring doorbell associated with a given endpoint. * ****************************************************************************/ static void xhci_ep_doorbell(FAR struct usbhost_xhci_s *priv, FAR struct xhci_epinfo_s *epinfo) { uint8_t target = xhci_epno_get(epinfo); uint32_t regval = 0; /* Doorbel tareget is EP number */ regval |= XHCI_DOORBEL_TARGET(target); /* Streams not supported yet (USB3.0 specific) */ regval |= XHCI_DOORBEL_TASK(0); /* Ring doorbell */ xhci_door_putreg(priv, XHCI_DOORBEL(epinfo->slot), regval); } /**************************************************************************** * Name: xhci_ioc_setup * * Description: * Set the request for the IOC event well BEFORE enabling the transfer (as * soon as we are absolutely committed to the transfer). We do * this to minimize race conditions. This logic would have to be expanded * if we want to have more than one packet in flight at a time! * * Assumption: * The caller holds the XHCI lock * ****************************************************************************/ static int xhci_ioc_setup(FAR struct xhci_rhport_s *rhport, FAR struct xhci_epinfo_s *epinfo, size_t buflen) { FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); irqstate_t flags; int ret = -ENODEV; DEBUGASSERT(rhport && epinfo && !epinfo->iocwait); #ifdef CONFIG_USBHOST_ASYNCH DEBUGASSERT(epinfo->callback == NULL); #endif /* Is the device still connected? */ flags = spin_lock_irqsave(&priv->spinlock); if (rhport->connected) { /* Then set iocwait to indicate that we expect to be informed when * either (1) the device is disconnected, or (2) the transfer * completed. */ epinfo->iocwait = true; /* We want to be awakened by IOC interrupt */ epinfo->status = 0; /* No status yet */ epinfo->xfrd = 0; /* Nothing transferred yet */ epinfo->buflen = buflen; /* Buffer length */ epinfo->result = -EBUSY; /* Transfer in progress */ #ifdef CONFIG_USBHOST_ASYNCH epinfo->callback = NULL; /* No asynchronous callback */ epinfo->arg = NULL; #endif ret = OK; /* We are good to go */ } spin_unlock_irqrestore(&priv->spinlock, flags); return ret; } /**************************************************************************** * Name: xhci_ioc_wait * * Description: * Wait for the IOC event. * * Assumption: * The caller does *NOT* hold the xHCI lock. That would cause a deadlock * when the bottom-half, worker thread needs to take the semaphore. * ****************************************************************************/ static int xhci_ioc_wait(FAR struct xhci_epinfo_s *epinfo) { int ret = OK; /* Wait for the IOC event. Loop to handle any false alarm semaphore * counts. Return an error if the task is canceled. */ while (epinfo->iocwait) { ret = nxsem_wait_uninterruptible(&epinfo->iocsem); if (ret < 0) { break; } } return ret < 0 ? ret : epinfo->result; } /**************************************************************************** * Name: xhci_control_setup * * Description: * Process a IN or OUT request control ep. * This function will enqueue the request and wait for it to * complete. Bulk data transfers differ in that req == NULL and there are * not SETUP or STATUS phases. * * This is a blocking function; it will not return until the control * transfer has completed. * * Assumption: * The caller holds the xHCI lock. * * Returned Value: * Zero (OK) is returned on success; a negated errno value is return on * any failure. * ****************************************************************************/ static int xhci_control_setup(FAR struct xhci_rhport_s *rhport, FAR struct xhci_epinfo_s *epinfo, FAR const struct usb_ctrlreq_s *req, FAR uint8_t *buffer, size_t buflen) { FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); struct xhci_trb_s trb[3]; uint8_t trt; int i = 0; /* Prepare Setup Stage TRB */ trb[i].d0 = *((FAR uint64_t *)req); trb[i].d1 = XHCI_TRB_D1_TXLEN_SET(8); trb[i].d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_SETUP_STAGE) | XHCI_TRB_D2_IDT; /* Reference: * - Table 4-7: USB SETUP Data to Data Stage TRB and Status Stage * TRB mapping */ if (req->type & USB_REQ_DIR_IN) { trt = XHCI_TRB_D2_TRT_INDATA; } else if (req->type & USB_REQ_DIR_OUT) { trt = XHCI_TRB_D2_TRT_OUTDATA; } else { trt = XHCI_TRB_D2_TRT_NODATA; } trb[i].d2 |= XHCI_TRB_D2_TRT_SET(trt); /* Next TRB */ i++; /* Prepare Data Stage TRB */ if (buffer) { trb[i].d0 = up_addrenv_va_to_pa(buffer); trb[i].d1 = XHCI_TRB_D1_TXLEN_SET(buflen); trb[i].d2 = XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_DATA_STAGE); if (req->type & USB_REQ_DIR_IN) { trb[i].d2 |= XHCI_TRB_D2_DIR; } /* Next TRB */ i++; } /* Prepare Status Stage TRB */ trb[i].d0 = 0; trb[i].d1 = XHCI_TRB_D1_IRQ_SET(0) | XHCI_TRB_D1_TXLEN_SET(0); trb[i].d2 = XHCI_TRB_D2_IOC | XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_STAT_STAGE); if (!(req->type & USB_REQ_DIR_IN)) { trb[i].d2 |= XHCI_TRB_D2_DIR; } /* Next TRB */ i++; /* Add TRBs to ring */ xhci_add_trb(priv, &epinfo->td, trb, i); /* Trigger transfer */ xhci_ep_doorbell(priv, epinfo); return OK; } /**************************************************************************** * Name: xhci_normal_setup * * Description: * Process a IN or OUT request on bulk or interrupt endpoint. * This function will enqueue the request and wait for it to complete. * * This is a blocking function; it will not return until the control * transfer has completed. * * Assumption: * The caller holds the xHCI lock. * * Returned Value: * Zero (OK) is returned on success; a negated errno value is returned on * any failure. * ****************************************************************************/ static int xhci_normal_setup(FAR struct xhci_rhport_s *rhport, FAR struct xhci_epinfo_s *epinfo, FAR uint8_t *buffer, size_t buflen) { FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); struct xhci_trb_s trb; /* Prepare TRB */ trb.d0 = up_addrenv_va_to_pa(buffer); trb.d1 = XHCI_TRB_D1_IRQ_SET(0) | XHCI_TRB_D1_TXLEN_SET(buflen); trb.d2 = XHCI_TRB_D2_IOC | XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_NORMAL); /* Add TRBs to ring */ xhci_add_trb(priv, &epinfo->td, &trb, 1); /* Trigger transfer */ xhci_ep_doorbell(priv, epinfo); return OK; } #ifndef CONFIG_USBHOST_ISOC_DISABLE /**************************************************************************** * Name: xhci_isoc_setup * * Description: * Process a request on isoch endpoint. * * Assumption: * The caller holds the xHCI lock. * * Returned Value: * Zero (OK) is returned on success; a negated errno value is returned on * any failure. * ****************************************************************************/ static int xhci_isoc_setup(FAR struct xhci_rhport_s *rhport, FAR struct xhci_epinfo_s *epinfo, FAR uint8_t *buffer, size_t buflen) { FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); struct xhci_trb_s trb; /* Prepare TRB */ trb.d0 = up_addrenv_va_to_pa(buffer); trb.d1 = XHCI_TRB_D1_IRQ_SET(0) | XHCI_TRB_D1_TXLEN_SET(buflen); trb.d2 = XHCI_TRB_D2_IOC | XHCI_TRB_D2_TYPE_SET(XHCI_TRB_TYPE_ISOCH); /* Start Isoch ASAP */ trb.d2 |= XHCI_TRB_D2_SIA; /* Add TRBs to ring */ xhci_add_trb(priv, &epinfo->td, &trb, 1); /* Trigger transfer */ xhci_ep_doorbell(priv, epinfo); return OK; } #endif /**************************************************************************** * Name: xhci_transfer_wait * * Description: * Wait for an IN or OUT transfer to complete. * * Assumption: * The caller holds the xHCI lock. The caller must be aware that the xHCI * lock will released while waiting for the transfer to complete, but will * be re-acquired when before returning. The state of xHCI resources could * be very different upon return. * * Returned Value: * On success, this function returns the number of bytes actually * transferred. For control transfers, this size includes the size of the * control request plus the size of the data (which could be short); for * bulk transfers, this will be the number of data bytes transfers (which * could be short). * ****************************************************************************/ static ssize_t xhci_transfer_wait(FAR struct usbhost_xhci_s *priv, FAR struct xhci_epinfo_s *epinfo) { int ret; /* Wait for the IOC completion event */ ret = xhci_ioc_wait(epinfo); /* Did xhci_ioc_wait() or nxmutex_lock report an error? */ if (ret < 0) { usbhost_trace1(XHCI_TRACE1_TRANSFER_FAILED, -ret); epinfo->iocwait = false; return (ssize_t)ret; } /* Transfer completed successfully. Return the number of bytes * transferred. */ return epinfo->xfrd; } #ifdef CONFIG_USBHOST_ASYNCH /**************************************************************************** * Name: xhci_ioc_async_setup * * Description: * Setup to receive an asynchronous notification when a transfer completes. * * Input Parameters: * epinfo - The IN or OUT endpoint descriptor for the device endpoint on * which the transfer will be performed. * callback - The function to be called when the transfer completes * arg - An arbitrary argument that will be provided with the callback. * * Returned Value: * None * * Assumptions: * - Called from the interrupt level * ****************************************************************************/ static inline int xhci_ioc_async_setup(FAR struct xhci_rhport_s *rhport, FAR struct xhci_epinfo_s *epinfo, usbhost_asynch_t callback, FAR void *arg) { FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_RHPORT(rhport); irqstate_t flags; int ret = -ENODEV; DEBUGASSERT(rhport && epinfo && !epinfo->iocwait && epinfo->callback == NULL); /* Is the device still connected? */ flags = spin_lock_irqsave(&priv->spinlock); if (rhport->connected) { /* Then save callback information to be used when either (1) the * device is disconnected, or (2) the transfer completes. */ epinfo->iocwait = false; /* No synchronous wakeup */ epinfo->status = 0; /* No status yet */ epinfo->xfrd = 0; /* Nothing transferred yet */ epinfo->result = -EBUSY; /* Transfer in progress */ epinfo->callback = callback; /* Asynchronous callback */ epinfo->arg = arg; /* Argument that accompanies the callback */ ret = OK; /* We are good to go */ } spin_unlock_irqrestore(&priv->spinlock, flags); return ret; } /**************************************************************************** * Name: xhci_asynch_completion * * Description: * This function is called at the interrupt level when an asynchronous * transfer completes. It performs the pending callback. * * Input Parameters: * epinfo - The IN or OUT endpoint descriptor for the device endpoint on * which the transfer was performed. * * Returned Value: * None * * Assumptions: * - Called from the interrupt level * ****************************************************************************/ static void xhci_asynch_completion(FAR struct xhci_epinfo_s *epinfo) { usbhost_asynch_t callback; ssize_t nbytes; FAR void *arg; int result; DEBUGASSERT(epinfo != NULL && epinfo->iocwait == false && epinfo->callback != NULL); /* Extract and reset the callback info */ callback = epinfo->callback; arg = epinfo->arg; result = epinfo->result; nbytes = epinfo->xfrd; epinfo->callback = NULL; epinfo->arg = NULL; epinfo->result = OK; epinfo->iocwait = false; /* Then perform the callback. Provide the number of bytes successfully * transferred or the negated errno value in the event of a failure. */ if (result < 0) { nbytes = (ssize_t)result; } callback(arg, nbytes); } #endif /**************************************************************************** * Name: xhci_portsc_work * * Description: * Handle Port Change work. * * Assumptions: * - Never called from an interrupt handler. * - Never called directly from xhci_events_poll() otherwise it gets stuck * on CLASS_DISCONNECTED() * ****************************************************************************/ static void xhci_portsc_work(FAR void *arg) { FAR struct usbhost_xhci_s *priv = arg; FAR struct usbhost_hubport_s *hport; FAR struct xhci_rhport_s *rhport; uint32_t portsc; int rhpndx; /* REVISIT: should this logic be protected? We can't use spinlock * here because we get stack in CLASS_DISCONNECTED(). */ /* Handle root hub status change on each root port */ for (rhpndx = 0; rhpndx < priv->no_ports; rhpndx++) { rhport = &priv->rhport[rhpndx]; portsc = xhci_oper_getreg(priv, XHCI_PORTSC(rhpndx)); usbhost_vtrace2(XHCI_VTRACE2_PORTSC, rhpndx + 1, portsc); /* Handle port connection status change (CSC) events */ if ((portsc & XHCI_PORTSC_CSC) != 0) { usbhost_vtrace1(XHCI_VTRACE1_PORTSC_CSC, portsc); /* Check current connect status */ if ((portsc & XHCI_PORTSC_CCS) != 0) { /* Connected ... Did we just become connected? */ if (!rhport->connected) { /* Yes.. connected. */ rhport->connected = true; usbhost_vtrace2(XHCI_VTRACE2_PORTSC_CONNECTED, rhpndx + 1, priv->pscwait); /* Notify any waiters */ if (priv->pscwait) { nxsem_post(&priv->pscsem); priv->pscwait = false; } } else { usbhost_vtrace1(XHCI_VTRACE1_PORTSC_CONNALREADY, portsc); } } else { /* Disconnected... Did we just become disconnected? */ if (rhport->connected) { /* Yes.. disconnect the device */ usbhost_vtrace2(XHCI_VTRACE2_PORTSC_DISCONND, rhpndx + 1, priv->pscwait); rhport->connected = false; /* Are we bound to a class instance? */ hport = &rhport->hport.hport; if (hport->devclass) { /* Yes.. Disconnect the class. */ CLASS_DISCONNECTED(hport->devclass); hport->devclass = NULL; } /* Notify any waiters for the Root Hub Status change * event. */ if (priv->pscwait) { nxsem_post(&priv->pscsem); priv->pscwait = false; } } else { usbhost_vtrace1(XHCI_VTRACE1_PORTSC_DISCALREADY, portsc); } } } /* Clear pending bit but don't touch PED ! */ portsc &= ~XHCI_PORTSC_PED; xhci_oper_putreg(priv, XHCI_PORTSC(rhpndx), portsc); } } /**************************************************************************** * Name: xhci_transfer_complete * * Description: * Handle transfer complete event * ****************************************************************************/ static void xhci_transfer_complete(FAR struct usbhost_xhci_s *priv, FAR struct xhci_trb_s *evt) { FAR struct xhci_epinfo_s *epinfo; uint32_t tl = XHCI_TRB_D1_TXLEN_GET(evt->d1); uint8_t slot = XHCI_TRB_D2_SLOTID_GET(evt->d2); uint8_t ep = XHCI_TRB_D2_EP_GET(evt->d2); uint8_t ret = XHCI_TRB_D1_CC_GET(evt->d1); irqstate_t flags; /* Get EP associated with this transfer */ epinfo = priv->devs[slot - 1].epinfo[ep - 1]; DEBUGASSERT(epinfo != NULL); flags = spin_lock_irqsave(&priv->spinlock); /* Get transferred length */ if (epinfo->buflen > 0) { epinfo->xfrd = epinfo->buflen - tl; } /* Check transfer status */ if (ret == XHCI_TRB_CC_SUCCESS) { /* Report success */ epinfo->status = 0; epinfo->result = OK; } else if (ret == XHCI_TRB_CC_STALL) { /* Report STALL condition */ epinfo->status = 0; epinfo->result = -EPERM; } else if (ret == XHCI_TRB_CC_SHORT_PKT) { /* Report success */ epinfo->status = 0; epinfo->result = OK; } else { /* Report error */ pcierr("transfer CC = %d\n", ret); epinfo->status = ret; epinfo->result = -EIO; } /* Is there a thread waiting for this transfer to complete? */ if (epinfo->iocwait) { /* Yes... wake it up */ epinfo->iocwait = 0; nxsem_post(&epinfo->iocsem); } #ifdef CONFIG_USBHOST_ASYNCH /* No.. Is there a pending asynchronous transfer? */ else if (epinfo->callback != NULL) { /* Yes.. perform the callback */ xhci_asynch_completion(epinfo); } #endif spin_unlock_irqrestore(&priv->spinlock, flags); } /**************************************************************************** * Name: xhci_envet_complete * * Description: * Handle event complete event * ****************************************************************************/ static void xhci_event_complete(FAR struct usbhost_xhci_s *priv, FAR struct xhci_trb_s *evt) { irqstate_t flags; /* REVISIT: we assume for now that only one command is pending */ /* Store command result */ flags = spin_lock_irqsave(&priv->spinlock); priv->cmdres.d0 = evt->d0; priv->cmdres.d1 = evt->d1; priv->cmdres.d2 = evt->d2; spin_unlock_irqrestore(&priv->spinlock, flags); /* Signal that command is done */ nxsem_post(&priv->cmdsem); } /**************************************************************************** * Name: xhci_events_poll * * Description: * Poll all pending events * ****************************************************************************/ static int xhci_events_poll(FAR struct usbhost_xhci_s *priv) { FAR struct xhci_trb_s *evt; uintptr_t addr; uint8_t type; uint32_t d2; /* Invalidate event ring */ up_invalidate_dcache((uintptr_t)priv->evnt.ring, (uintptr_t)(priv->evnt.ring + XHCI_EVENT_MAX)); /* Handle all pending events */ while (1) { evt = &priv->evnt.ring[priv->evnt.i]; /* Update address */ addr = (uintptr_t)evt; d2 = le32toh(evt->d2); if ((d2 & XHCI_TRB_D2_C) != priv->evnt.ccs) { break; } type = XHCI_TRB_D2_TYPE_GET(d2); switch (type) { /* Transfer Event */ case XHCI_TRB_EVT_TRANSFER: { xhci_transfer_complete(priv, evt); break; } /* Command Completion Event */ case XHCI_TRB_EVT_CMD_COMP: { xhci_event_complete(priv, evt); break; } /* Port Status Change Event */ case XHCI_TRB_EVT_PSTAT_CHANGE: { /* We have to handle Port Status Change in a separate work * queue, otherwise we'll get stuck when handling disconnect * request. */ if (work_available(&priv->pscwork)) { work_queue(LPWORK, &priv->pscwork, xhci_portsc_work, (FAR void *)priv, 0); } break; } default: { pciinfo("ignored event %d\n", type); break; } } /* Next event */ priv->evnt.i++; /* Handle ring wrap */ if (priv->evnt.i >= XHCI_EVENT_MAX) { xhci_ring_reset(&priv->evnt, true); } } /* Clear ERDP busy bit and update dequeue pointer */ addr = up_addrenv_va_to_pa((FAR void *)addr); addr |= XHCI_ERDP_EHB; xhci_runt_putreg_8b(priv, XHCI_ERDP(0), addr); return OK; } /**************************************************************************** * Name: xhci_interupt_work * * Description: * Handle xHCI interrupts * ****************************************************************************/ static void xhci_interrupt_work(FAR void *arg) { FAR struct usbhost_xhci_s *priv = arg; uint32_t iman; xhci_events_poll(priv); /* Port Change Detect */ if (priv->pending & XHCI_USBSTS_PCD) { /* Handled as event in xhci_events_poll() */ pciinfo("Port Change Detect\n"); } /* Host Controller Halted */ if (priv->pending & XHCI_USBSTS_HCH) { pciinfo("Host Controller Halted\n"); } /* Host System Error */ if (priv->pending & XHCI_USBSTS_HSE) { pciinfo("Host System Error\n"); } /* Host Controller Error */ if (priv->pending & XHCI_USBSTS_HCE) { pciinfo("Host Controller Error\n"); } /* ACK interrupts */ xhci_oper_putreg(priv, XHCI_USBSTS, priv->pending); /* Clear interrupter pending bit */ iman = xhci_runt_getreg(priv, XHCI_IMAN(0)); if (iman & XHCI_IMAN_IP) { xhci_runt_putreg(priv, XHCI_IMAN(0), iman); } /* Clear pending bits */ priv->pending = 0; } /**************************************************************************** * Name: xhci_interupt * * Description: * Interrupt handler for xHCI * ****************************************************************************/ static int xhci_interrupt(int irq, FAR void *context, FAR void *arg) { FAR struct usbhost_xhci_s *priv = arg; /* Get pending interrupts */ priv->pending = xhci_oper_getreg(priv, XHCI_USBSTS); /* Handle interrupts in worker */ if (work_available(&priv->work)) { work_queue(HPWORK, &priv->work, xhci_interrupt_work, arg, 0); } return OK; } /**************************************************************************** * Name: xhci_wait * * Description: * Wait for a device to be connected or disconnected to/from a hub port. * * Input Parameters: * conn - The USB host connection instance obtained as a parameter from * the call to the USB driver initialization logic. * hport - The location to return the hub port descriptor that detected * the connection related event. * * Returned Value: * Zero (OK) is returned on success when a device is connected or * disconnected. This function will not return until either (1) a device is * connected or disconnect to/from any hub port or until (2) some failure * occurs. On a failure, a negated errno value is returned indicating the * nature of the failure * * Assumptions: * - Called from a single thread so no mutual exclusion is required. * - Never called from an interrupt handler. * ****************************************************************************/ static int xhci_wait(FAR struct usbhost_connection_s *conn, FAR struct usbhost_hubport_s **hport) { FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_CONN(conn); FAR struct xhci_rhport_s *rhport; FAR struct usbhost_hubport_s *connport; irqstate_t flags; int rhpndx; int ret; /* Loop until the connection state changes on one of the root hub ports or * until an error occurs. */ while (true) { flags = spin_lock_irqsave(&priv->spinlock); /* Check for a change in the connection state on any root hub port */ for (rhpndx = 0; rhpndx < priv->no_ports; rhpndx++) { /* Has the connection state changed on the RH port? */ rhport = &priv->rhport[rhpndx]; connport = &rhport->hport.hport; if (rhport->connected != connport->connected) { /* Yes.. Return the RH port to inform the caller which * port has the connection change. */ connport->connected = rhport->connected; *hport = connport; spin_unlock_irqrestore(&priv->spinlock, flags); usbhost_vtrace2(XHCI_VTRACE2_MONWAKEUP, rhpndx + 1, rhport->connected); return OK; } } #ifdef CONFIG_USBHOST_HUB /* Is a device connected to an external hub? */ if (priv->hport) { /* Yes.. return the external hub port */ connport = priv->hport; priv->hport = NULL; *hport = (FAR struct usbhost_hubport_s *)connport; spin_unlock_irqrestore(&priv->spinlock, flags); usbhost_vtrace2(XHCI_VTRACE2_MONWAKEUP, HPORT(connport), connport->connected); return OK; } #endif /* No changes on any port. Wait for a connection/disconnection event * and check again */ priv->pscwait = true; spin_unlock_irqrestore(&priv->spinlock, flags); ret = nxsem_wait_uninterruptible(&priv->pscsem); if (ret < 0) { return ret; } } } /**************************************************************************** * Name: xhci_rh_enumerate/xhci_enumerate * * Description: * Enumerate the connected device. As part of this enumeration process, * the driver will (1) get the device's configuration descriptor, (2) * extract the class ID info from the configuration descriptor, (3) call * usbhost_findclass() to find the class that supports this device, (4) * call the create() method on the struct usbhost_registry_s interface * to get a class instance, and finally (5) call the connect() method * of the struct usbhost_class_s interface. After that, the class is in * charge of the sequence of operations. * * Input Parameters: * conn - The USB host connection instance obtained as a parameter from * the call to the USB driver initialization logic. * hport - The descriptor of the hub port that has the newly connected * device. * * Returned Value: * On success, zero (OK) is returned. On a failure, a negated errno value * is returned indicating the nature of the failure * * Assumptions: * This function will *not* be called from an interrupt handler. * ****************************************************************************/ static int xhci_rh_enumerate(FAR struct usbhost_connection_s *conn, FAR struct usbhost_hubport_s *hport) { FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_CONN(conn); FAR struct xhci_rhport_s *rhport; int rhpndx; int ret; DEBUGASSERT(hport != NULL); rhpndx = hport->port; DEBUGASSERT(rhpndx >= 0 && rhpndx < priv->no_ports); rhport = &priv->rhport[rhpndx]; /* Are we connected to a device? The caller should have called the wait() * method first to be assured that a device is connected. */ if (!rhport->connected) { /* No, return an error */ pcierr("not connected\n"); return -ENODEV; } /* Enable port */ ret = xhci_port_enable(priv, hport); if (ret < 0) { pcierr("Failed to enable port %d\n", ret); return ret; } /* Initialize device data */ ret = xhci_device_init(priv, rhport); if (ret < 0) { pcierr("Failed to initialize device %d\n", ret); return ret; } return OK; } /**************************************************************************** * Name: xhci_enumerate * * Description: * See description above. * ****************************************************************************/ static int xhci_enumerate(FAR struct usbhost_connection_s *conn, FAR struct usbhost_hubport_s *hport) { int ret; /* If this is a connection on the root hub, then we need to go to * little more effort to get the device speed. If it is a connection * on an external hub, then we already have that information. */ DEBUGASSERT(hport); #ifdef CONFIG_USBHOST_HUB if (ROOTHUB(hport)) #endif { ret = xhci_rh_enumerate(conn, hport); if (ret < 0) { return ret; } } /* Then let the common usbhost_enumerate do the real enumeration. */ ret = usbhost_enumerate(hport, &hport->devclass); if (ret < 0) { /* Failed to enumerate */ /* If this is a root hub port, then marking the hub port not connected * will cause xhci_wait() to return and we will try the connection * again. */ hport->connected = false; } return ret; } /**************************************************************************** * Name: xhci_ep0configure * * Description: * Configure endpoint 0. This method is normally used internally by the * enumerate() method but is made available at the interface to support * an external implementation of the enumeration logic. * * Input Parameters: * drvr - The USB host driver instance obtained as a parameter from the * call to the class create() method. * funcaddr - The USB address of the function containing the endpoint that * EP0 controls. A funcaddr of zero will be received if no address is * yet assigned to the device. * speed - The speed of the port USB_SPEED_LOW, _FULL, or _HIGH * maxpacketsize - The maximum number of bytes that can be sent to or * received from the endpoint in a single data packet * * Returned Value: * On success, zero (OK) is returned. On a failure, a negated errno value * is returned indicating the nature of the failure. * * Assumptions: * This function will *not* be called from an interrupt handler. * ****************************************************************************/ static int xhci_ep0configure(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, uint8_t funcaddr, uint8_t speed, uint16_t maxpacketsize) { FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep0; FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); uint64_t ctx; int ret; DEBUGASSERT(drvr != NULL && epinfo != NULL && maxpacketsize < 2048); ret = nxmutex_lock(&priv->lock); if (ret >= 0) { /* Update max packet size */ rhport->dev->input->ep[0].ctx1 &= ~XHCI_EP_CTX1_MAXPKT_MASK; rhport->dev->input->ep[0].ctx1 |= XHCI_EP_CTX1_MAXPKT(maxpacketsize); /* Add Slot Context and EP0 Context */ xhci_context_ctrl(priv, rhport->dev, 0, XHCI_IN_CTX1_A(XHCI_SLOT_FLAG) | XHCI_IN_CTX1_A(XHCI_EP0_FLAG)); /* Flush Device input context */ up_flush_dcache((uintptr_t)rhport->dev->input, (uintptr_t)rhport->dev->input + sizeof(struct xhci_input_dev_ctx_s)); /* Free mutex before command execution */ nxmutex_unlock(&priv->lock); ctx = up_addrenv_va_to_pa(rhport->dev->input); ret = xhci_cmd_evalctx(priv, epinfo->slot, ctx); } return ret; } /**************************************************************************** * Name: xhci_epalloc * * Description: * Allocate and configure one endpoint. * * Input Parameters: * drvr - The USB host driver instance obtained as a parameter from the * call to the class create() method. * epdesc - Describes the endpoint to be allocated. * ep - A memory location provided by the caller in which to receive the * allocated endpoint descriptor. * * Returned Value: * On success, zero (OK) is returned. On a failure, a negated errno value * is returned indicating the nature of the failure. * * Assumptions: * This function will *not* be called from an interrupt handler. * ****************************************************************************/ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr, FAR const struct usbhost_epdesc_s *epdesc, FAR usbhost_ep_t *ep) { FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; FAR struct usbhost_hubport_s *hport; FAR struct xhci_epinfo_s *epinfo; FAR struct xhci_dev_s *dev; uint32_t mask; uint8_t eptype; uint8_t idx; int ret; /* Sanity check. NOTE that this method should only be called if a device * is connected (because we need a valid low speed indication). */ DEBUGASSERT(drvr != 0 && epdesc != NULL && epdesc->hport != NULL && ep != NULL); hport = epdesc->hport; /* Terse output only if we are tracing */ #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", epdesc->addr, epdesc->in ? "IN" : "OUT", hport->funcaddr, epdesc->xfrtype, epdesc->interval, epdesc->mxpacketsize); #endif /* Allocate a endpoint information structure */ epinfo = kmm_zalloc(sizeof(struct xhci_epinfo_s)); if (!epinfo) { return -ENOMEM; } /* Initialize the endpoint container (which is really just another form of * 'struct usbhost_epdesc_s', packed differently and with additional * information. A cleaner design might just embed struct usbhost_epdesc_s * inside of struct xhci_epinfo_s and just memcpy here. */ epinfo->dirin = epdesc->in; epinfo->epno = epdesc->addr; #ifndef CONFIG_USBHOST_INT_DISABLE epinfo->interval = epdesc->interval; #endif epinfo->xfrtype = epdesc->xfrtype; nxsem_init(&epinfo->iocsem, 0, 0); /* xhci_epno_get() returns Device Context Index (DCI) */ idx = xhci_epno_get(epinfo); mask = XHCI_IN_CTX1_A(XHCI_EP_FLAG(idx)); dev = rhport->dev; dev->epinfo[idx - 1] = epinfo; /* TD rings already allocated but not connected yet. */ ret = xhci_ring_init(&epinfo->td, XHCI_TD_MAX); if (ret < 0) { pcierr("ep ring init failed\n"); return ret; } /* Store slot ID for later */ epinfo->slot = rhport->slot; #ifdef CONFIG_USBHOST_HUB if (hport->speed != USB_SPEED_HIGH) { /* A high speed hub exists between this device and the root hub * otherwise we would not get here. */ FAR struct usbhost_hubport_s *parent = hport->parent; for (; parent->speed != USB_SPEED_HIGH; parent = hport->parent) { hport = parent; } if (parent->speed == USB_SPEED_HIGH) { epinfo->hubport = HPORT(hport); epinfo->hubaddr = hport->parent->funcaddr; } else { return -EINVAL; } } #endif /* Get EP type */ switch (epinfo->xfrtype) { case USB_EP_ATTR_XFER_BULK: { eptype = epinfo->dirin ? XHCI_EPTYPE_BULK_IN : XHCI_EPTYPE_BULK_OUT; break; } #ifndef CONFIG_USBHOST_INT_DISABLE case USB_EP_ATTR_XFER_INT: #endif { eptype = epinfo->dirin ? XHCI_EPTYPE_INTR_IN : XHCI_EPTYPE_INTR_OUT; break; } #ifndef CONFIG_USBHOST_ISOC_DISABLE case USB_EP_ATTR_XFER_ISOC: { eptype = epinfo->dirin ? XHCI_EPTYPE_ISO_IN : XHCI_EPTYPE_ISO_OUT; break; } #endif default: { return -ENOSYS; } } /* REVISIT: do we need disable EP here? */ /* Initialize EP context. * Max Burst Size set for 0 for now (USB3.0 specific) */ xhci_ep_configure(priv, &dev->input->ep[idx - 1], eptype, epdesc->mxpacketsize, 0, up_addrenv_va_to_pa(epinfo->td.ring), 0, epinfo->interval); /* Evaluate the slot context */ xhci_context_ctrl(priv, dev, 0, mask | XHCI_IN_CTX1_A(XHCI_SLOT_FLAG)); up_flush_dcache((uintptr_t)dev->input, (uintptr_t)dev->input + sizeof(struct xhci_input_dev_ctx_s)); /* Configure EP */ ret = xhci_cmd_cfgep(priv, epinfo->slot, up_addrenv_va_to_pa(dev->input), false); if (ret < 0) { pcierr("failed to configure EP %d\n", ret); return ret; } /* Success.. return an opaque reference to the endpoint information * structure instance */ *ep = (usbhost_ep_t)epinfo; return OK; } /**************************************************************************** * Name: xhci_epfree * * Description: * Free an endpoint previously allocated by DRVR_EPALLOC. * * Input Parameters: * drvr - The USB host driver instance obtained as a parameter from the * call to the class create() method. * ep - The endpoint to be freed. * * Returned Value: * On success, zero (OK) is returned. On a failure, a negated errno value * is returned indicating the nature of the failure * * Assumptions: * This function will *not* be called from an interrupt handler. * ****************************************************************************/ static int xhci_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) { FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; /* There should not be any pending, transfers */ DEBUGASSERT(drvr && epinfo && epinfo->iocwait == 0); /* Free ring */ xhci_ring_deinit(&epinfo->td); /* Free the container */ kmm_free(epinfo); return OK; } /**************************************************************************** * Name: xhci_alloc * * Description: * Some hardware supports special memory in which request and descriptor * data can be accessed more efficiently. This method provides a * mechanism to allocate the request/descriptor memory. If the underlying * hardware does not support such "special" memory, this functions may * simply map to kmm_malloc(). * * This interface was optimized under a particular assumption. It was * assumed that the driver maintains a pool of small, pre-allocated buffers * for descriptor traffic. NOTE that size is not an input, but an output: * The size of the pre-allocated buffer is returned. * * Input Parameters: * drvr - The USB host driver instance obtained as a parameter from the * call to the class create() method. * buffer - The address of a memory location provided by the caller in * which to return the allocated buffer memory address. * maxlen - The address of a memory location provided by the caller in * which to return the maximum size of the allocated buffer memory. * * Returned Value: * On success, zero (OK) is returned. On a failure, a negated errno value * is returned indicating the nature of the failure * * Assumptions: * - Called from a single thread so no mutual exclusion is required. * - Never called from an interrupt handler. * ****************************************************************************/ static int xhci_alloc(FAR struct usbhost_driver_s *drvr, FAR uint8_t **buffer, FAR size_t *maxlen) { int ret = -ENOMEM; DEBUGASSERT(drvr && buffer && maxlen); /* Allocated buffer must not cross page boundaries */ *buffer = (FAR uint8_t *)kmm_memalign((XHCI_PAGE_SIZE / 2) , XHCI_BUFSIZE); if (*buffer) { *maxlen = XHCI_BUFSIZE; ret = OK; } return ret; } /**************************************************************************** * Name: xhci_free * * Description: * Some hardware supports special memory in which request and descriptor * data can be accessed more efficiently. This method provides a * mechanism to free that request/descriptor memory. If the underlying * hardware does not support such "special" memory, this functions may * simply map to kmm_free(). * * Input Parameters: * drvr - The USB host driver instance obtained as a parameter from the * call to the class create() method. * buffer - The address of the allocated buffer memory to be freed. * * Returned Value: * On success, zero (OK) is returned. On a failure, a negated errno value * is returned indicating the nature of the failure * * Assumptions: * - Never called from an interrupt handler. * ****************************************************************************/ static int xhci_free(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer) { DEBUGASSERT(drvr && buffer); /* No special action is require to free the transfer/descriptor buffer * memory */ kmm_free(buffer); return OK; } /**************************************************************************** * Name: xhci_ioalloc * * Description: * Some hardware supports special memory in which larger IO buffers can * be accessed more efficiently. This method provides a mechanism to * allocate the request/descriptor memory. If the underlying hardware * does not support such "special" memory, this functions may simply map * to kumm_malloc. * * This interface differs from DRVR_ALLOC in that the buffers are variable- * sized. * * Input Parameters: * drvr - The USB host driver instance obtained as a parameter from the * call to the class create() method. * buffer - The address of a memory location provided by the caller in * which to return the allocated buffer memory address. * buflen - The size of the buffer required. * * Returned Value: * On success, zero (OK) is returned. On a failure, a negated errno value * is returned indicating the nature of the failure * * Assumptions: * This function will *not* be called from an interrupt handler. * ****************************************************************************/ static int xhci_ioalloc(FAR struct usbhost_driver_s *drvr, FAR uint8_t **buffer, size_t buflen) { int ret = -ENOMEM; DEBUGASSERT(drvr && buffer && buflen > 0); /* Large transfers are not supported now */ if (buflen > XHCI_PAGE_SIZE) { return -ENOMEM; } /* Allocated buffer must not cross page boundaries */ *buffer = (FAR uint8_t *)kmm_memalign((XHCI_PAGE_SIZE / 2) , buflen); if (*buffer) { ret = OK; } return ret; } /**************************************************************************** * Name: xhci_iofree * * Description: * Some hardware supports special memory in which IO data can be accessed * more efficiently. This method provides a mechanism to free that IO * buffer memory. If the underlying hardware does not support such * "special" memory, this functions may simply map to kumm_free(). * * Input Parameters: * drvr - The USB host driver instance obtained as a parameter from the * call to the class create() method. * buffer - The address of the allocated buffer memory to be freed. * * Returned Value: * On success, zero (OK) is returned. On a failure, a negated errno value * is returned indicating the nature of the failure * * Assumptions: * This function will *not* be called from an interrupt handler. * ****************************************************************************/ static int xhci_iofree(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer) { DEBUGASSERT(drvr && buffer); /* No special action is require to free the transfer/descriptor buffer * memory */ kmm_free(buffer); return OK; } /**************************************************************************** * Name: xhci_ctrl_xfer * * Description: * Process a IN or OUT request on the control endpoint. These methods * will enqueue the request and wait for it to complete. Only one * transfer may be queued; Neither these methods nor the transfer() method * can be called again until the control transfer function returns. * * These are blocking methods; these functions will not return until the * control transfer has completed. * * Input Parameters: * drvr - The USB host driver instance obtained as a parameter from the * call to the class create() method. * ep0 - The control endpoint to send/receive the control request. * req - Describes the request to be sent. This request must lie in * memory created by DRVR_ALLOC. * buffer - A buffer used for sending the request and for returning any * responses. This buffer must be large enough to hold the * length value in the request description. buffer must have been * allocated using DRVR_ALLOC. * * NOTE: On an IN transaction, req and buffer may refer to the xHCI * allocated memory. * * Returned Value: * On success, zero (OK) is returned. On a failure, a negated errno value * is returned indicating the nature of the failure * * Assumptions: * - Called from a single thread so no mutual exclusion is required. * - Never called from an interrupt handler. * ****************************************************************************/ static int xhci_ctrl_xfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, FAR const struct usb_ctrlreq_s *req, FAR uint8_t *buffer) { FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; FAR struct xhci_epinfo_s *ep0info = (FAR struct xhci_epinfo_s *)ep0; uint16_t len; ssize_t nbytes; int ret; DEBUGASSERT(rhport != NULL && ep0info != NULL && req != NULL); len = xhci_getle16(req->len); /* Terse output only if we are tracing */ #ifdef CONFIG_USBHOST_TRACE usbhost_vtrace2(XHCI_VTRACE2_CTRLINOUT, RHPORT(rhport), req->req); #endif /* Special case for SET_ADDRESS request */ if (req->req == USB_REQ_SETADDRESS) { /* Reset EP0 ring to its initial state, so when xHCI update EP0 * context, TD dequeue pointer would be valid. It may be already * off, because the USB Host stack has already sent some messages * on control EP. */ xhci_ring_init(&rhport->dev->rhport->ep0.td, 0); /* Issue SET_ADDRESS request */ ret = xhci_address_set(priv, rhport, true); if (ret == OK) { /* Store USB Device Address assigned by xHCI */ ep0info->devaddr = XHCI_ST_CTX3_ADDR_GET(rhport->dev->ctx->slot.ctx[3]); rhport->dev->input->slot.ctx[3] = rhport->dev->ctx->slot.ctx[3]; } return OK; } /* We must have exclusive access to the XHCI hardware and data * structures. */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { return ret; } /* Set the request for the IOC event well BEFORE initiating the transfer. */ ret = xhci_ioc_setup(rhport, ep0info, 0); if (ret != OK) { goto errout_with_lock; } /* Now initiate the transfer */ ret = xhci_control_setup(rhport, ep0info, req, buffer, len); if (ret < 0) { pcierr("ERROR: xhci_control_setup failed: %d\n", ret); goto errout_with_iocwait; } nxmutex_unlock(&priv->lock); /* And wait for the transfer to complete */ nbytes = xhci_transfer_wait(priv, ep0info); return nbytes >= 0 ? OK : (int)nbytes; errout_with_iocwait: ep0info->iocwait = false; errout_with_lock: nxmutex_unlock(&priv->lock); return ret; } /**************************************************************************** * Name: xhci_ctrlin * * Description: * Process IN request on the control endpoint. For details, see * description for xhci_ctrl_xfer(). * ****************************************************************************/ static int xhci_ctrlin(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, FAR const struct usb_ctrlreq_s *req, FAR uint8_t *buffer) { /* xhci_ctrl_xfer() can handle both directions */ return xhci_ctrl_xfer(drvr, ep0, req, buffer); } /**************************************************************************** * Name: xhci_ctrlout * * Description: * Process OUT request on the control endpoint. For details, see * description for xhci_ctrl_xfer(). * ****************************************************************************/ static int xhci_ctrlout(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0, FAR const struct usb_ctrlreq_s *req, FAR const uint8_t *buffer) { /* xhci_ctrl_xfer() can handle both directions. We just need to work around * the differences in the function signatures. */ return xhci_ctrl_xfer(drvr, ep0, req, (FAR uint8_t *)buffer); } /**************************************************************************** * Name: xhci_transfer * * Description: * Process a request to handle a transfer descriptor. This method will * enqueue the transfer request, blocking until the transfer completes. * Only one transfer may be queued; Neither this method nor the ctrlin or * ctrlout methods can be called again until this function returns. * * This is a blocking method; this functions will not return until the * transfer has completed. * * Input Parameters: * drvr - The USB host driver instance obtained as a parameter from the * call to the class create() method. * ep - The IN or OUT endpoint descriptor for the device endpoint on * which to perform the transfer. * buffer - A buffer containing the data to be sent (OUT endpoint) or * received (IN endpoint). buffer must have been allocated using * DRVR_ALLOC * buflen - The length of the data to be sent or received. * * Returned Value: * On success, a non-negative value is returned that indicates the number * of bytes successfully transferred. On a failure, a negated errno value * is returned that indicates the nature of the failure: * * EAGAIN - If devices NAKs the transfer (or NYET or other error where * it may be appropriate to restart the entire transaction). * EPERM - If the endpoint stalls * EIO - On a TX or data toggle error * EPIPE - Overrun errors * * Assumptions: * - Called from a single thread so no mutual exclusion is required. * - Never called from an interrupt handler. * ****************************************************************************/ static ssize_t xhci_transfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, FAR uint8_t *buffer, size_t buflen) { FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; ssize_t nbytes; int ret; DEBUGASSERT(priv && rhport && epinfo && buffer && buflen > 0); /* We must have exclusive access to the xHCI hardware and data * structures. */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { return (ssize_t)ret; } /* Set the request for the IOC event well BEFORE initiating the transfer. */ ret = xhci_ioc_setup(rhport, epinfo, buflen); if (ret != OK) { goto errout_with_lock; } /* Initiate the transfer */ switch (epinfo->xfrtype) { case USB_EP_ATTR_XFER_BULK: #ifndef CONFIG_USBHOST_INT_DISABLE case USB_EP_ATTR_XFER_INT: #endif { ret = xhci_normal_setup(rhport, epinfo, buffer, buflen); break; } #ifndef CONFIG_USBHOST_ISOC_DISABLE case USB_EP_ATTR_XFER_ISOC: { ret = xhci_isoc_setup(rhport, epinfo, buffer, buflen); break; } #endif case USB_EP_ATTR_XFER_CONTROL: default: { usbhost_trace1(XHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype); ret = -ENOSYS; break; } } /* Check for errors in the setup of the transfer */ if (ret < 0) { uerr("ERROR: Transfer setup failed: %d\n", ret); goto errout_with_iocwait; } nxmutex_unlock(&priv->lock); /* Then wait for the transfer to complete */ nbytes = xhci_transfer_wait(priv, epinfo); return nbytes; errout_with_iocwait: epinfo->iocwait = false; errout_with_lock: nxmutex_unlock(&priv->lock); return (ssize_t)ret; } /**************************************************************************** * Name: xhci_asynch * * Description: * Process a request to handle a transfer descriptor. This method will * enqueue the transfer request and return immediately. When the transfer * completes, the callback will be invoked with the provided transfer. * This method is useful for receiving interrupt transfers which may come * infrequently. * * Only one transfer may be queued; Neither this method nor the ctrlin or * ctrlout methods can be called again until the transfer completes. * * Input Parameters: * drvr - The USB host driver instance obtained as a parameter from * the call to the class create() method. * ep - The IN or OUT endpoint descriptor for the device endpoint on * which to perform the transfer. * buffer - A buffer containing the data to be sent (OUT endpoint) or * received (IN endpoint). buffer must have been allocated * using DRVR_ALLOC * buflen - The length of the data to be sent or received. * callback - This function will be called when the transfer completes. * arg - The arbitrary parameter that will be passed to the callback * function when the transfer completes. * * Returned Value: * On success, zero (OK) is returned. On a failure, a negated errno value * is returned indicating the nature of the failure * * Assumptions: * - Called from a single thread so no mutual exclusion is required. * - Never called from an interrupt handler. * ****************************************************************************/ #ifdef CONFIG_USBHOST_ASYNCH static int xhci_asynch(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, FAR uint8_t *buffer, size_t buflen, usbhost_asynch_t callback, FAR void *arg) { FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; int ret; DEBUGASSERT(priv && rhport && epinfo && buffer && buflen > 0); /* We must have exclusive access to the xHCI hardware and data * structures. */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { return ret; } /* Set the request for the callback well BEFORE initiating the transfer. */ ret = xhci_ioc_async_setup(rhport, epinfo, callback, arg); if (ret != OK) { goto errout_with_lock; } /* Initiate the transfer */ switch (epinfo->xfrtype) { case USB_EP_ATTR_XFER_BULK: #ifndef CONFIG_USBHOST_INT_DISABLE case USB_EP_ATTR_XFER_INT: #endif { ret = xhci_normal_setup(rhport, epinfo, buffer, buflen); break; } #ifndef CONFIG_USBHOST_ISOC_DISABLE case USB_EP_ATTR_XFER_ISOC: { ret = xhci_isoc_setup(rhport, epinfo, buffer, buflen); break; } #endif case USB_EP_ATTR_XFER_CONTROL: default: { usbhost_trace1(XHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype); ret = -ENOSYS; break; } } /* Check for errors in the setup of the transfer */ if (ret < 0) { goto errout_with_callback; } /* The transfer is in progress */ nxmutex_unlock(&priv->lock); return OK; errout_with_callback: epinfo->callback = NULL; epinfo->arg = NULL; errout_with_lock: nxmutex_unlock(&priv->lock); return ret; } #endif /* CONFIG_USBHOST_ASYNCH */ /**************************************************************************** * Name: xhci_cancel * * Description: * Cancel a pending transfer on an endpoint. Cancelled synchronous or * asynchronous transfer will complete normally with the error -ESHUTDOWN. * * Input Parameters: * drvr - The USB host driver instance obtained as a parameter from the * call to the class create() method. * ep - The IN or OUT endpoint descriptor for the device endpoint on which * an asynchronous transfer should be transferred. * * Returned Value: * On success, zero (OK) is returned. On a failure, a negated errno value * is returned indicating the nature of the failure. * ****************************************************************************/ static int xhci_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) { FAR struct xhci_epinfo_s *epinfo = (FAR struct xhci_epinfo_s *)ep; FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); #ifdef CONFIG_USBHOST_ASYNCH usbhost_asynch_t callback; FAR void *arg; #endif irqstate_t flags; bool iocwait; DEBUGASSERT(epinfo); /* Sample and reset all transfer termination information. This will * prevent any callbacks from occurring while we performing the * cancellation. The transfer may still be in progress, however, so this * does not eliminate other DMA-related race conditions. */ flags = spin_lock_irqsave(&priv->spinlock); #ifdef CONFIG_USBHOST_ASYNCH callback = epinfo->callback; arg = epinfo->arg; #endif iocwait = epinfo->iocwait; #ifdef CONFIG_USBHOST_ASYNCH epinfo->callback = NULL; epinfo->arg = NULL; #endif epinfo->iocwait = false; spin_unlock_irqrestore(&priv->spinlock, flags); /* Bail if there is no transfer in progress for this endpoint */ #ifdef CONFIG_USBHOST_ASYNCH if (callback == NULL && !iocwait) #else if (!iocwait) #endif { return OK; } /* Stop endpoint */ xhci_cmd_stopep(priv, epinfo->slot, xhci_epno_get(epinfo), false); /* REVISIT: what if we interrupted the execution of a TD? page 139 */ epinfo->result = -ESHUTDOWN; if (iocwait) { /* Yes... wake it up */ nxsem_post(&epinfo->iocsem); } #ifdef CONFIG_USBHOST_ASYNCH /* No.. Is there a pending asynchronous transfer? */ else { /* Yes.. perform the callback */ DEBUGASSERT(callback != NULL); callback(arg, -ESHUTDOWN); } #endif return OK; } /**************************************************************************** * Name: xhci_connect * * Description: * New connections may be detected by an attached hub. This method is the * mechanism that is used by the hub class to introduce a new connection * and port description to the system. * * Input Parameters: * drvr - The USB host driver instance obtained as a parameter from the * call to the class create() method. * hport - The descriptor of the hub port that detected the connection * related event * connected - True: device connected; false: device disconnected * * Returned Value: * On success, zero (OK) is returned. On a failure, a negated errno value * is returned indicating the nature of the failure. * ****************************************************************************/ #ifdef CONFIG_USBHOST_HUB static int xhci_connect(FAR struct usbhost_driver_s *drvr, FAR struct usbhost_hubport_s *hport, bool connected) { #error missing logic } #endif /**************************************************************************** * Name: xhci_disconnect * * Description: * Called by the class when an error occurs and device has been * disconnected. The USB host driver should discard the handle to the * class instance (it is stale) and not attempt any further interaction * with the class driver instance (until a new instance is received from * the create() method). The driver should not call the class * disconnected() method. * * Input Parameters: * drvr - The USB host driver instance obtained as a parameter from the * call to the class create() method. * hport - The port from which the device is being disconnected. Might be * a port on a hub. * * Returned Value: * None * * Assumptions: * - Only a single class bound to a single device is supported. * - Never called from an interrupt handler. * ****************************************************************************/ static void xhci_disconnect(FAR struct usbhost_driver_s *drvr, FAR struct usbhost_hubport_s *hport) { FAR struct usbhost_xhci_s *priv = XHCI_PRIV_FROM_DRVR(drvr); FAR struct xhci_rhport_s *rhport = (FAR struct xhci_rhport_s *)drvr; DEBUGASSERT(hport != NULL); hport->devclass = NULL; /* Deinit device slot */ if (rhport->dev) { xhci_device_deinit(priv, rhport); } } /**************************************************************************** * Name: xhci_hw_getparams * * Description: * Get hardware description of a connected xHCI device. * ****************************************************************************/ static int xhci_hw_getparams(FAR struct usbhost_xhci_s *priv) { uint32_t regval; /* Get data form Host Controller Capability 1 Parameters */ regval = xhci_capa_getreg(priv, XHCI_HCCPARAMS1); if (regval & XHCI_HCCPARAMS1_CSZ) { pcierr("Only 32 byte Context data structures supported!\n"); return -EIO; } /* Get data from Structural Parameters 1 register */ regval = xhci_capa_getreg(priv, XHCI_HCSPARAMS1); priv->no_slots = XHCI_HCSPARAMS1_MAXSLOTS(regval); priv->no_ports = XHCI_HCSPARAMS1_MAXPORTS(regval); /* Limit number of slots to number of devices */ if (priv->no_slots > CONFIG_USBHOST_XHCI_MAX_DEVS) { priv->no_slots = CONFIG_USBHOST_XHCI_MAX_DEVS; } pciinfo("no slots = %d, no ports = %d\n", priv->no_slots, priv->no_ports); /* Check if valid */ if (priv->no_slots == 0 || priv->no_ports == 0) { return -EINVAL; } /* Get data from Structural Parameters 2 register */ regval = xhci_capa_getreg(priv, XHCI_HCSPARAMS2); priv->no_scratch = XHCI_HCSPARAMS2_MAXSPB(regval); pciinfo("no scratch = %d\n", priv->no_scratch); priv->no_erst = 1 << XHCI_HCSPARAMS2_ERST(regval); pciinfo("no_erst = %d\n", priv->no_erst); /* Limit event ring segment table to 1 */ if (priv->no_erst > XHCI_MAX_ERST) { priv->no_erst = XHCI_MAX_ERST; } pciinfo("no erst = %d\n", priv->no_erst); return OK; } /**************************************************************************** * Name: xhci_irq_initialize * * Description: * Initialize xHCI interrupts - require MSI-X support. * ****************************************************************************/ 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; } /**************************************************************************** * Name: xhci_mem_alloc * * Description: * Allocated memory for a new detected xHCI device. * ****************************************************************************/ static int xhci_mem_alloc(FAR struct usbhost_xhci_s *priv) { size_t tmp; int i; /* Allocate Scratchpad Buffer Array */ tmp = priv->no_scratch * sizeof(uint64_t); priv->pg_sb = kmm_memalign(XHCI_BUF_ALIGN, tmp); if (!priv->pg_sb) { pcierr("pg_sb malloc failed\n"); return -ENOMEM; } memset(priv->pg_sb, 0, tmp); for (i = 0; i < priv->no_scratch; i++) { /* Alloc page for each entry in array */ priv->pg_sb[i] = up_addrenv_va_to_pa( kmm_memalign(XHCI_PAGE_SIZE, XHCI_PAGE_SIZE)); if (!priv->pg_sb[i]) { pcierr("pg_sb[i] malloc failed\n"); return -ENOMEM; } /* Reset page */ memset((FAR void *)(up_addrenv_pa_to_va(priv->pg_sb[i])), 0, XHCI_PAGE_SIZE); } /* Allocate Device Context Array which shall be: * size = MaxSlotsEn + 1 entries */ tmp = sizeof(uint64_t) * (priv->no_slots + 1); priv->pg_ctx = kmm_memalign(XHCI_BUF_ALIGN, tmp); if (!priv->pg_ctx) { pcierr("pg_ctx malloc failed\n"); return -ENOMEM; } /* Reset context */ memset(priv->pg_ctx, 0, tmp); /* Allocate Event Table */ tmp = sizeof(struct xhci_event_ring_s) * priv->no_erst; priv->pg_erst = kmm_memalign(XHCI_BUF_ALIGN, tmp); if (!priv->pg_erst) { pcierr("priv->pg_erst malloc failed\n"); return -ENOMEM; } memset(priv->pg_erst, 0, tmp); /* Allocate root hub ports */ priv->rhport = kmm_zalloc(priv->no_ports * sizeof(struct xhci_rhport_s)); if (!priv->rhport) { pcierr("rhport zalloc failed!\n"); return -ENOMEM; } /* Allocate xHC devices array */ priv->devs = kmm_zalloc(priv->no_slots * sizeof(struct xhci_dev_s)); if (!priv->devs) { pcierr("devs zalloc failed!\n"); return -ENOMEM; } /* Allocate xHC devices resources */ for (i = 0; i < priv->no_slots; i++) { /* Allocate Device Context */ priv->devs[i].ctx = kmm_zalloc(sizeof(struct xhci_dev_ctx_s)); if (!priv->devs[i].ctx) { pcierr("dev ctx zalloc failed!\n"); return -ENOMEM; } /* Allocate Input Context. The Input Context shall be physically * contiguous within a page */ priv->devs[i].input = kmm_memalign((XHCI_PAGE_SIZE / 2), sizeof(struct xhci_input_dev_ctx_s)); if (!priv->devs[i].input) { pcierr("dev input zalloc failed!\n"); return -ENOMEM; } /* No endpoint for device yet */ tmp = sizeof(uintptr_t) * XHCI_MAX_ENDPOINTS; memset(priv->devs[i].epinfo, 0, tmp); } return OK; } /**************************************************************************** * Name: xhci_mem_free * * Description: * Free allocated memory for a xHCI device. * ****************************************************************************/ static int xhci_mem_free(FAR struct usbhost_xhci_s *priv) { int i; /* Free scratch buffers */ for (i = 0; i < priv->no_scratch; i++) { kmm_free((FAR void *)priv->pg_sb[i]); } kmm_free(priv->pg_sb); /* Free devices */ for (i = 0; i < priv->no_slots; i++) { kmm_free(priv->devs[i].ctx); kmm_free(priv->devs[i].input); } kmm_free(priv->devs); kmm_free(priv->pg_ctx); kmm_free(priv->pg_erst); kmm_free(priv->rhport); /* Free command ring and event ring */ xhci_ring_deinit(&priv->cmd); xhci_ring_deinit(&priv->evnt); return OK; } /**************************************************************************** * Name: xhci_hw_initialize * * Description: * One-time setup of the host controller hardware for normal operations. * * Input Parameters: * priv -- USB host driver private data structure. * * Returned Value: * Zero on success; a negated errno value on failure. * ****************************************************************************/ static int xhci_hw_initialize(FAR struct usbhost_xhci_s *priv) { int ret; /* Synchronize with BIOS */ ret = xhci_bios_wait(priv); if (ret < 0) { pcierr("Failed to get xhci controller!\n"); goto errout; } /* Get structural parameters */ ret = xhci_hw_getparams(priv); if (ret < 0) { goto errout; } /* Allocate all required memory */ ret = xhci_mem_alloc(priv); if (ret < 0) { goto errout; } /* Configure interrupts */ ret = xhci_irq_initialize(priv); if (ret < 0) { goto errout; } /* Halt controller */ ret = xhci_ctrl_halt(priv); if (ret < 0) { goto errout; } errout: return ret; } /**************************************************************************** * Name: xhci_sw_initialize * * Description: * One-time setup of the host driver state structure. * * Input Parameters: * priv -- USB host driver private data structure. * * Returned Value: * None. * ****************************************************************************/ static inline int xhci_sw_initialize(FAR struct usbhost_xhci_s *priv) { FAR struct xhci_rhport_s *rhport; FAR struct usbhost_hubport_s *hport; int i; /* Initialize sync objects */ nxmutex_init(&priv->lock); nxsem_init(&priv->pscsem, 0, 0); nxsem_init(&priv->cmdsem, 0, 0); /* Initialize function address generation logic * REVISIT: xHCI hardware is responsible for device address, but NuttX USB * Host stack require this to be initialized. */ usbhost_devaddr_initialize(&priv->devgen); /* Initialize devices */ for (i = 0; i < priv->no_slots; i++) { /* Slot disabled by defaulte */ priv->devs[i].state = XHCI_SLOT_DISABLED; } /* Initialize the root hub port structures */ for (i = 0; i < priv->no_ports; i++) { rhport = &priv->rhport[i]; /* No device slot yet */ rhport->dev = NULL; /* Connect xhci instance */ rhport->priv = priv; /* Initialize the device operations */ rhport->drvr.ep0configure = xhci_ep0configure; rhport->drvr.epalloc = xhci_epalloc; rhport->drvr.epfree = xhci_epfree; rhport->drvr.alloc = xhci_alloc; rhport->drvr.free = xhci_free; rhport->drvr.ioalloc = xhci_ioalloc; rhport->drvr.iofree = xhci_iofree; rhport->drvr.ctrlin = xhci_ctrlin; rhport->drvr.ctrlout = xhci_ctrlout; rhport->drvr.transfer = xhci_transfer; #ifdef CONFIG_USBHOST_ASYNCH rhport->drvr.asynch = xhci_asynch; #endif rhport->drvr.cancel = xhci_cancel; #ifdef CONFIG_USBHOST_HUB rhport->drvr.connect = xhci_connect; #endif rhport->drvr.disconnect = xhci_disconnect; rhport->hport.pdevgen = &priv->devgen; /* Initialize EP0 */ rhport->ep0.xfrtype = USB_EP_ATTR_XFER_CONTROL; rhport->ep0.epno = 0; rhport->ep0.devaddr = 0; nxsem_init(&rhport->ep0.iocsem, 0, 0); /* Initialize the public port representation */ hport = &rhport->hport.hport; hport->drvr = &rhport->drvr; #ifdef CONFIG_USBHOST_HUB hport->parent = NULL; #endif hport->ep0 = &rhport->ep0; hport->port = i; hport->speed = USB_SPEED_FULL; } return OK; } /**************************************************************************** * Name: pci_xhci_probe * * Description: * Initialize PCI device. * ****************************************************************************/ static int pci_xhci_probe(FAR struct pci_device_s *dev) { FAR struct usbhost_conn_xhci_s *conn = NULL; FAR struct usbhost_xhci_s *priv = NULL; int ret = -ENOMEM; /* Init PCI bus */ pci_set_master(dev); pciinfo("Enabled bus mastering\n"); pci_enable_device(dev); pciinfo("Enabled memory resources\n"); /* Allocate connection structure */ conn = kmm_zalloc(sizeof(struct usbhost_conn_xhci_s)); if (!conn) { pcierr("zalloc failed!\n"); goto errout; } /* Allocate the driver structure */ priv = kmm_zalloc(sizeof(struct usbhost_xhci_s)); if (!priv) { pcierr("zalloc failed!\n"); goto errout; } /* Initialize connection data */ conn->conn.wait = xhci_wait; conn->conn.enumerate = xhci_enumerate; conn->priv = priv; /* Connect PCI handler */ 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 */ priv->capa_base = priv->base; priv->oper_base = priv->base + xhci_capa_getreg_1b(priv, XHCI_CAPLENGTH); priv->runt_base = priv->base + xhci_capa_getreg(priv, XHCI_RTSOFF); priv->door_base = priv->base + xhci_capa_getreg(priv, XHCI_DBOFF); usbhost_vtrace1(XHCI_VTRACE1_INITIALIZING, 0); /* Initialize HW */ ret = xhci_hw_initialize(priv); if (ret < 0) { pcierr("failed to initialize HW!\n"); goto errout; } /* Initialize SW */ ret = xhci_sw_initialize(priv); if (ret < 0) { pcierr("failed to initialize SW!\n"); goto errout; } /* Start controller */ ret = xhci_ctrl_start(priv); if (ret < 0) { usbhost_trace1(XHCI_TRACE1_START_FAILED, 0); goto errout; } #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. */ 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"); goto errout; } /* Store waiter PID */ conn->pid = ret; return OK; errout: /* Free allocated xhci buffers */ xhci_mem_free(priv); /* Free allocated data */ kmm_free(conn); kmm_free(priv); return ret; } /**************************************************************************** * Name: pci_xhci_remove * * Description: * Remove PCI device. * ****************************************************************************/ static void pci_xhci_remove(FAR struct pci_device_s *dev) { FAR struct usbhost_conn_xhci_s *conn = dev->priv; FAR struct usbhost_xhci_s *priv = conn->priv; /* Free xhci interrupts */ irq_detach(priv->irq); pci_release_irq(dev, &priv->irq, 1); /* Disable PCI devicve */ pci_clear_master(dev); pci_disable_device(dev); /* Delete waiter thread */ kthread_delete(conn->pid); /* Free xhci buffers */ 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); }