diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 2c49416391..cfafb329ec 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -12,6 +12,13 @@ menuconfig PCI if PCI +config PCI_MSIX + bool "PCI MSI-X support" + default n + ---help--- + Enables support for PCI MSI-X. When enabled, MSI-X takse priority + over MSI when a device supports it. + config PCI_ASSIGN_ALL_BUSES bool "Assign resource to all buses" default !ARCH_X86 && !ARCH_X86_64 diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index ebef78ea93..dfd39a8b9e 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -450,7 +450,9 @@ static uint8_t pci_bus_find_start_cap(FAR struct pci_bus_s *bus, return 0; } - switch (hdr_type) + /* Ignore MF bit */ + + switch (hdr_type & 0x7f) { case PCI_HEADER_TYPE_NORMAL: case PCI_HEADER_TYPE_BRIDGE: @@ -1056,6 +1058,244 @@ static void pci_scan_bus(FAR struct pci_bus_s *bus) } } +/**************************************************************************** + * Name: pci_get_msi_base + * + * Description: + * Get MSI and MSI-X base + * + * Input Parameters: + * dev - device + * msi - returned MSI base + * msix - returned MSI-X base + * + * Return value: + * None + * + ****************************************************************************/ + +static void pci_get_msi_base(FAR struct pci_device_s *dev, FAR uint8_t *msi, + FAR uint8_t *msix) +{ + if (msi != NULL) + { + *msi = pci_find_capability(dev, PCI_CAP_ID_MSI); + } + + if (msix != NULL) + { + *msix = pci_find_capability(dev, PCI_CAP_ID_MSIX); + } +} + +/**************************************************************************** + * Name: pci_enable_msi + * + * Description: + * Configure and enable MSI. + * + * Input Parameters: + * dev - device + * irq - allocated vectors + * num - number of vectors + * msi - MSI base address + * + * Return value: + * OK on success or a negative error code on failure + * + ****************************************************************************/ + +static int pci_enable_msi(FAR struct pci_device_s *dev, FAR int *irq, + int num, uint8_t msi) +{ + uint32_t mdr = 0; + uint16_t flags = 0; + uintptr_t mar = 0; + uint16_t mme = 0; + uint32_t mmc = 0; + int ret = OK; + + /* Suppoted messages */ + + for (mme = 0; (1 << mme) < num; mme++); + + /* Get Message Control Register */ + + pci_read_config_word(dev, msi + PCI_MSI_FLAGS, &flags); + mmc = (flags & PCI_MSI_FLAGS_QMASK) >> PCI_MSI_FLAGS_QMASK_SHIFT; + if (mme > mmc) + { + mme = mmc; + num = 1 << mme; + pciinfo("Limit MME to %x, num to %d\n", mmc, num); + } + + /* Configure MSI (arch-specific) */ + + ret = dev->bus->ctrl->ops->connect_irq(dev->bus, irq, num, &mar, &mdr); + if (ret < 0) + { + return ret; + } + + /* Write Message Address Regsiter */ + + pci_write_config_dword(dev, msi + PCI_MSI_ADDRESS_LO, mar); + + /* Write Message Data Register */ + + if ((flags & PCI_MSI_FLAGS_64BIT) != 0) + { + pci_write_config_dword(dev, msi + PCI_MSI_ADDRESS_HI, (mar >> 32)); + pci_write_config_dword(dev, msi + PCI_MSI_DATA_64, mdr); + } + else + { + pci_write_config_word(dev, msi + PCI_MSI_DATA_32, mdr); + } + + flags |= mme << PCI_MSI_FLAGS_QSIZE_SHIFT; + + /* Enable MSI */ + + flags |= PCI_MSI_FLAGS_ENABLE; + + /* Write Message Control Register */ + + pci_write_config_word(dev, msi + PCI_MSI_FLAGS, flags); + return OK; +} + +#ifdef CONFIG_PCI_MSIX +/**************************************************************************** + * Name: pci_disable_msi + * + * Description: + * Disable MSI. + * + * Input Parameters: + * dev - device + * msi - MSI base address + * + * Return value: + * None + * + ****************************************************************************/ + +static void pci_disable_msi(FAR struct pci_device_s *dev, uint8_t msi) +{ + uint16_t flags = 0; + + pci_read_config_word(dev, msi + PCI_MSI_FLAGS, &flags); + + flags &= ~PCI_MSI_FLAGS_ENABLE; + pci_write_config_word(dev, msi + PCI_MSI_FLAGS, flags); +} + +/**************************************************************************** + * Name: pci_enable_msix + * + * Description: + * Configure and enable MSI-X. + * + * Input Parameters: + * dev - device + * irq - allocated vectors + * num - number of vectors + * msix - MSI-X base address + * + * Return value: + * OK on success or a negative error code on failure + * + ****************************************************************************/ + +static int pci_enable_msix(FAR struct pci_device_s *dev, FAR int *irq, + int num, uint8_t msix) +{ + uint32_t mdr = 0; + uint16_t flags = 0; + uintptr_t mar = 0; + uintptr_t tbladdr = 0; + uintptr_t tblend = 0; + uint32_t tbloffset = 0; + uint32_t tblbar = 0; + uint32_t tbl = 0; + uint16_t tblsize = 0; + int i = 0; + int ret = OK; + + /* Get Flags */ + + pci_read_config_word(dev, msix + PCI_MSIX_FLAGS, &flags); + + /* Table Size is N - 1 encoded */ + + tblsize = (flags & PCI_MSIX_FLAGS_QSIZE) + 1; + + /* Get MSI-X table */ + + pci_read_config_dword(dev, msix + PCI_MSIX_TABLE, &tbl); + + /* Extract table address */ + + tblbar = tbl & PCI_MSIX_TABLE_BIR; + tbladdr = pci_resource_start(dev, tblbar); + tbloffset = (tbl & PCI_MSIX_TABLE_OFFSET) >> PCI_MSIX_TABLE_OFFSET_SHIFT; + tbladdr += tbloffset; + + /* Map MSI-X table */ + + tblend = tbladdr + tblsize * PCI_MSIX_ENTRY_SIZE; + tbladdr = dev->bus->ctrl->ops->map(dev->bus, tbladdr, tblend); + + /* Limit tblsize */ + + if (num > tblsize) + { + pciinfo("Limit tblszie to %xu\n", tblsize); + num = tblsize; + } + + for (i = 0; i < num; i++) + { + /* Connect MSI-X (arch-specific) */ + + ret = dev->bus->ctrl->ops->connect_irq(dev->bus, &irq[i], 1, + &mar, &mdr); + if (ret < 0) + { + return ret; + } + + /* Write Message Address Register */ + + pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_LOWER_ADDR, mar); + + pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_UPPER_ADDR, + (mar >> 32)); + + /* Write Message Data Register */ + + pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_DATA, mdr); + + /* Write Vector Control register */ + + pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_VECTOR_CTRL, 0); + + /* Next vector */ + + tbladdr += PCI_MSIX_ENTRY_SIZE; + } + + /* Enable MSI-X */ + + flags |= PCI_MSIX_FLAGS_ENABLE; + pci_write_config_word(dev, msix + PCI_MSIX_FLAGS, flags); + + return OK; +} +#endif /* CONFIG_PCI_MSIX */ + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -1408,6 +1648,154 @@ uint8_t pci_find_next_capability(FAR struct pci_device_s *dev, uint8_t pos, pos + PCI_CAP_LIST_NEXT, cap); } +/**************************************************************************** + * Name: pci_stat_line + * + * Description: + * Determine if the interrupt line is active for a given device + * + * Input Parameters: + * dev - device + * + * Return value: + * True if interrupt is active + * + ****************************************************************************/ + +bool pci_stat_line(FAR struct pci_device_s *dev) +{ + uint16_t tmp1; + uint16_t tmp2; + + /* Interrupts enabled if Interrupt Disable is not set and Interrupt Status + * is set. + */ + + pci_read_config_word(dev, PCI_COMMAND, &tmp1); + pci_read_config_word(dev, PCI_STATUS, &tmp2); + + return (!(tmp1 & PCI_COMMAND_INTX_DISABLE) && + (tmp2 & PCI_STATUS_INTERRUPT)); +} + +/**************************************************************************** + * Name: pci_get_irq + * + * Description: + * Get interrupt number associated with a device PCI interrupt line + * + * Input Parameters: + * dev - PCI device + * + * Return value: + * Return interrupt number associated with a given INTx. + * + ****************************************************************************/ + +int pci_get_irq(FAR struct pci_device_s *dev) +{ + uint8_t line = 0; + + pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &line); + return dev->bus->ctrl->ops->get_irq(dev->bus, line); +} + +/**************************************************************************** + * Name: pci_alloc_irq + * + * Description: + * Allocate MSI or MSI-X vectors + * + * Input Parameters: + * dev - PCI device + * irq - allocated vectors + * num - number of vectors + * + * Return value: + * Return the number of allocated vectors on succes or negative errno + * on failure. + * + ****************************************************************************/ + +int pci_alloc_irq(FAR struct pci_device_s *dev, FAR int *irq, int num) +{ + return dev->bus->ctrl->ops->alloc_irq(dev->bus, irq, num); +} + +/**************************************************************************** + * Name: pci_release_irq + * + * Description: + * Release MSI or MSI-X vectors + * + * Input Parameters: + * dev - PCI device + * irq - allocated vectors + * num - number of vectors + * + * Return value: + * Failed if return a negative value, otherwise success + * + ****************************************************************************/ + +void pci_release_irq(FAR struct pci_device_s *dev, FAR int *irq, int num) +{ + dev->bus->ctrl->ops->release_irq(dev->bus, irq, num); +} + +/**************************************************************************** + * Name: pci_connect_irq + * + * Description: + * Connect MSI or MSI-X if available. + * + * Input Parameters: + * dev - PCI device + * irq - allocated vectors + * num - number of vectors + * + * Return value: + * Return -ENOSETUP if MSI/MSI-X not available. Return OK on success. + * + ****************************************************************************/ + +int pci_connect_irq(FAR struct pci_device_s *dev, FAR int *irq, int num) +{ + uint8_t msi = 0; + uint8_t msix = 0; + + /* Get MSI base */ + + pci_get_msi_base(dev, &msi, &msix); + if (msi == 0 && msix == 0) + { + /* MSI and MSI-X not supported */ + + return -ENOTSUP; + } + + /* Configure MSI or MSI-X */ + +#ifdef CONFIG_PCI_MSIX + if (msix != 0) + { + /* Disalbe MSI */ + + pci_disable_msi(dev, msi); + + /* Enable MSI-X */ + + return pci_enable_msix(dev, irq, num, msix); + } + else +#endif + { + /* Enable MSI */ + + return pci_enable_msi(dev, irq, num, msi); + } +} + /**************************************************************************** * Name: pci_register_driver * diff --git a/include/nuttx/pci/pci.h b/include/nuttx/pci/pci.h index b03168f9db..5635aa8f03 100644 --- a/include/nuttx/pci/pci.h +++ b/include/nuttx/pci/pci.h @@ -170,6 +170,24 @@ #define pci_write_io_dword(dev, addr, val) \ pci_bus_write_io_dword((dev)->bus, addr, val) +#define pci_write_mmio_byte(dev, addr, val) \ + (*((FAR volatile uint8_t *)(addr))) = val + +#define pci_write_mmio_word(dev, addr, val) \ + (*((FAR volatile uint16_t *)(addr))) = val + +#define pci_write_mmio_dword(dev, addr, val) \ + (*((FAR volatile uint32_t *)(addr))) = val + +#define pci_read_mmio_byte(dev, addr, val) \ + (*val) = *((FAR volatile uint8_t *)(addr)) + +#define pci_read_mmio_word(dev, addr, val) \ + (*val) = *((FAR volatile uint16_t *)(addr)) + +#define pci_read_mmio_dword(dev, addr, val) \ + (*val) = *((FAR volatile uint32_t *)(addr)) + /**************************************************************************** * Public Types ****************************************************************************/ @@ -254,6 +272,21 @@ struct pci_ops_s int size, FAR uint32_t *val); CODE int (*write_io)(FAR struct pci_bus_s *bus, uintptr_t addr, int size, uint32_t val); + + /* Get interrupt number associated with a given INTx line */ + + CODE int (*get_irq)(FAR struct pci_bus_s *bus, uint8_t line); + + /* Allocate interrupt for MSI/MSI-X */ + + CODE int (*alloc_irq)(FAR struct pci_bus_s *bus, FAR int *irq, int num); + + CODE void (*release_irq)(FAR struct pci_bus_s *bus, FAR int *irq, int num); + + /* Connect interrupt for MSI/MSI-X */ + + CODE int (*connect_irq)(FAR struct pci_bus_s *bus, FAR int *irq, + int num, FAR uintptr_t *mar, FAR uint32_t *mdr); }; /* Each pci channel is a top-level PCI bus seem by CPU. A machine with @@ -522,6 +555,93 @@ uint8_t pci_find_capability(FAR struct pci_device_s *dev, int cap); uint8_t pci_find_next_capability(FAR struct pci_device_s *dev, uint8_t pos, int cap); +/**************************************************************************** + * Name: pci_stat_line + * + * Description: + * Determine if the interrupt line is active for a given device + * + * Input Parameters: + * dev - device + * + * Return value: + * True if interrupt is active + * + ****************************************************************************/ + +bool pci_stat_line(FAR struct pci_device_s *dev); + +/**************************************************************************** + * Name: pci_get_irq + * + * Description: + * Get interrupt number associated with a device PCI interrupt line + * + * Input Parameters: + * dev - PCI device + * + * Return value: + * Return interrupt number associated with a given INTx. + * + ****************************************************************************/ + +int pci_get_irq(FAR struct pci_device_s *dev); + +/**************************************************************************** + * Name: pci_alloc_irq + * + * Description: + * Allocate MSI or MSI-X vectors + * + * Input Parameters: + * dev - PCI device + * irq - allocated vectors + * num - number of vectors + * + * Return value: + * Return the number of allocated vectors on succes or negative errno + * on failure. + * + ****************************************************************************/ + +int pci_alloc_irq(FAR struct pci_device_s *dev, FAR int *irq, int num); + +/**************************************************************************** + * Name: pci_release_irq + * + * Description: + * Release MSI or MSI-X vectors + * + * Input Parameters: + * dev - PCI device + * irq - allocated vectors + * num - number of vectors + * + * Return value: + * None + * + ****************************************************************************/ + +void pci_release_irq(FAR struct pci_device_s *dev, FAR int *irq, int num); + +/**************************************************************************** + * Name: pci_connect_irq + * + * Description: + * Connect MSI or MSI-X if available. + * + * Input Parameters: + * dev - PCI device + * irq - allocated vectors + * num - number of vectors + * + * Return value: + * Return -ENOSETUP if MSI/MSI-X not available. Return OK on success. + * + ****************************************************************************/ + +int pci_connect_irq(FAR struct pci_device_s *dev, FAR int *irq, int num); + /**************************************************************************** * Name: pci_register_driver * diff --git a/include/nuttx/pci/pci_regs.h b/include/nuttx/pci/pci_regs.h index a1d4488dd2..f7e168dd14 100644 --- a/include/nuttx/pci/pci_regs.h +++ b/include/nuttx/pci/pci_regs.h @@ -313,7 +313,9 @@ #define PCI_MSI_FLAGS 2 /* Message Control */ #define PCI_MSI_FLAGS_ENABLE 0x0001 /* MSI feature enabled */ #define PCI_MSI_FLAGS_QMASK 0x000e /* Maximum queue size available */ +#define PCI_MSI_FLAGS_QMASK_SHIFT 0x0001 #define PCI_MSI_FLAGS_QSIZE 0x0070 /* Message queue size configured */ +#define PCI_MSI_FLAGS_QSIZE_SHIFT 0x0004 #define PCI_MSI_FLAGS_64BIT 0x0080 /* 64-bit addresses allowed */ #define PCI_MSI_FLAGS_MASKBIT 0x0100 /* Per-vector masking capable */ @@ -323,6 +325,7 @@ #define PCI_MSI_ADDRESS_LO 4 /* Lower 32 bits */ #define PCI_MSI_ADDRESS_HI 8 /* Upper 32 bits (if PCI_MSI_FLAGS_64BIT set) */ #define PCI_MSI_DATA_32 8 /* 16 bits of data for 32-bit devices */ +#define PCI_MSI_DATA_CPUID_SHIFT 12 /* Destination CPU ID */ #define PCI_MSI_MASK_32 12 /* Mask bits register for 32-bit devices */ #define PCI_MSI_PENDING_32 16 /* Pending intrs for 32-bit devices */ #define PCI_MSI_DATA_64 12 /* 16 bits of data for 64-bit devices */ @@ -338,6 +341,7 @@ #define PCI_MSIX_TABLE 4 /* Table offset */ #define PCI_MSIX_TABLE_BIR 0x00000007 /* BAR index */ #define PCI_MSIX_TABLE_OFFSET 0xfffffff8 /* Offset into specified BAR */ +#define PCI_MSIX_TABLE_OFFSET_SHIFT 3 #define PCI_MSIX_PBA 8 /* Pending Bit Array offset */ #define PCI_MSIX_PBA_BIR 0x00000007 /* BAR index */ #define PCI_MSIX_PBA_OFFSET 0xfffffff8 /* Offset into specified BAR */