mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-10-31 16:54:21 +00:00 
			
		
		
		
	Merge branch 'remotes/lorenzo/pci/dwc'
- Add Kirin MSI support (Xiaowei Song)
  - Drop unnecessary root_bus_nr setting from exynos, imx6, keystone,
    armada8k, artpec6, designware-plat, histb, qcom, spear13xx (Shawn Guo)
  - Move link notification settings from DesignWare core to individual
    drivers (Gustavo Pimentel)
  - Add endpoint library MSI-X interfaces (Gustavo Pimentel)
  - Correct signature of endpoint library IRQ interfaces (Gustavo Pimentel)
  - Add DesignWare endpoint library MSI-X callbacks (Gustavo Pimentel)
  - Add endpoint library MSI-X test support (Gustavo Pimentel)
* remotes/lorenzo/pci/dwc:
  PCI: endpoint: Add MSI set maximum restriction
  tools: PCI: Add MSI-X support
  pci_endpoint_test: Add 2 ioctl commands
  pci-epf-test/pci_endpoint_test: Add MSI-X support
  pci-epf-test/pci_endpoint_test: Use irq_type module parameter
  pci-epf-test/pci_endpoint_test: Cleanup PCI_ENDPOINT_TEST memspace
  PCI: dwc: Add legacy interrupt callback handler
  PCI: dwc: Rework MSI callbacks handler
  PCI: dwc: Add MSI-X callbacks handler
  PCI: Update xxx_pcie_ep_raise_irq() and pci_epc_raise_irq() signatures
  PCI: endpoint: Add MSI-X interfaces
  PCI: dwc: Fix EP link notification implementation
  PCI: spear13xx: Drop unnecessary root_bus_nr setting
  PCI: qcom: Drop unnecessary root_bus_nr setting
  PCI: histb: Drop unnecessary root_bus_nr setting
  PCI: designware-plat: Drop unnecessary root_bus_nr setting
  PCI: artpec6: Drop unnecessary root_bus_nr setting
  PCI: armada8k: Drop unnecessary root_bus_nr setting
  PCI: keystone: Drop unnecessary root_bus_nr setting
  PCI: imx6: Drop unnecessary root_bus_nr setting
  PCI: exynos: Drop unnecessary root_bus_nr setting
  PCI: kirin: Add MSI support
			
			
This commit is contained in:
		
						commit
						0c38011aba
					
				
					 30 changed files with 759 additions and 155 deletions
				
			
		|  | @ -15,3 +15,5 @@ subsys_id	 : don't care | |||
| interrupt_pin	 : Should be 1 - INTA, 2 - INTB, 3 - INTC, 4 -INTD | ||||
| msi_interrupts	 : Should be 1 to 32 depending on the number of MSI interrupts | ||||
| 		   to test | ||||
| msix_interrupts	 : Should be 1 to 2048 depending on the number of MSI-X | ||||
| 		   interrupts to test | ||||
|  |  | |||
|  | @ -44,7 +44,7 @@ by the PCI controller driver. | |||
| 	 * clear_bar: ops to reset the BAR | ||||
| 	 * alloc_addr_space: ops to allocate in PCI controller address space | ||||
| 	 * free_addr_space: ops to free the allocated address space | ||||
| 	 * raise_irq: ops to raise a legacy or MSI interrupt | ||||
| 	 * raise_irq: ops to raise a legacy, MSI or MSI-X interrupt | ||||
| 	 * start: ops to start the PCI link | ||||
| 	 * stop: ops to stop the PCI link | ||||
| 
 | ||||
|  | @ -96,7 +96,7 @@ by the PCI endpoint function driver. | |||
| *) pci_epc_raise_irq() | ||||
| 
 | ||||
|    The PCI endpoint function driver should use pci_epc_raise_irq() to raise | ||||
|    Legacy Interrupt or MSI Interrupt. | ||||
|    Legacy Interrupt, MSI or MSI-X Interrupt. | ||||
| 
 | ||||
| *) pci_epc_mem_alloc_addr() | ||||
| 
 | ||||
|  |  | |||
|  | @ -20,6 +20,8 @@ The PCI endpoint test device has the following registers: | |||
| 	5) PCI_ENDPOINT_TEST_DST_ADDR | ||||
| 	6) PCI_ENDPOINT_TEST_SIZE | ||||
| 	7) PCI_ENDPOINT_TEST_CHECKSUM | ||||
| 	8) PCI_ENDPOINT_TEST_IRQ_TYPE | ||||
| 	9) PCI_ENDPOINT_TEST_IRQ_NUMBER | ||||
| 
 | ||||
| *) PCI_ENDPOINT_TEST_MAGIC | ||||
| 
 | ||||
|  | @ -34,10 +36,10 @@ that the endpoint device must perform. | |||
| Bitfield Description: | ||||
|   Bit 0		: raise legacy IRQ | ||||
|   Bit 1		: raise MSI IRQ | ||||
|   Bit 2 - 7	: MSI interrupt number | ||||
|   Bit 8		: read command (read data from RC buffer) | ||||
|   Bit 9		: write command (write data to RC buffer) | ||||
|   Bit 10	: copy command (copy data from one RC buffer to another | ||||
|   Bit 2		: raise MSI-X IRQ | ||||
|   Bit 3		: read command (read data from RC buffer) | ||||
|   Bit 4		: write command (write data to RC buffer) | ||||
|   Bit 5		: copy command (copy data from one RC buffer to another | ||||
| 		  RC buffer) | ||||
| 
 | ||||
| *) PCI_ENDPOINT_TEST_STATUS | ||||
|  | @ -64,3 +66,22 @@ COPY/READ command. | |||
| 
 | ||||
| This register contains the destination address (RC buffer address) for | ||||
| the COPY/WRITE command. | ||||
| 
 | ||||
| *) PCI_ENDPOINT_TEST_IRQ_TYPE | ||||
| 
 | ||||
| This register contains the interrupt type (Legacy/MSI) triggered | ||||
| for the READ/WRITE/COPY and raise IRQ (Legacy/MSI) commands. | ||||
| 
 | ||||
| Possible types: | ||||
|  - Legacy	: 0 | ||||
|  - MSI		: 1 | ||||
|  - MSI-X	: 2 | ||||
| 
 | ||||
| *) PCI_ENDPOINT_TEST_IRQ_NUMBER | ||||
| 
 | ||||
| This register contains the triggered ID interrupt. | ||||
| 
 | ||||
| Admissible values: | ||||
|  - Legacy	: 0 | ||||
|  - MSI		: [1 .. 32] | ||||
|  - MSI-X	: [1 .. 2048] | ||||
|  |  | |||
|  | @ -45,9 +45,9 @@ The PCI endpoint framework populates the directory with the following | |||
| configurable fields. | ||||
| 
 | ||||
| 	# ls functions/pci_epf_test/func1 | ||||
| 	  baseclass_code	interrupt_pin	revid		subsys_vendor_id | ||||
| 	  cache_line_size	msi_interrupts	subclass_code	vendorid | ||||
| 	  deviceid          	progif_code	subsys_id | ||||
| 	  baseclass_code	interrupt_pin	progif_code	subsys_id | ||||
| 	  cache_line_size	msi_interrupts	revid		subsys_vendorid | ||||
| 	  deviceid          	msix_interrupts	subclass_code	vendorid | ||||
| 
 | ||||
| The PCI endpoint function driver populates these entries with default values | ||||
| when the device is bound to the driver. The pci-epf-test driver populates | ||||
|  | @ -67,6 +67,7 @@ device, the following commands can be used. | |||
| 	# echo 0x104c > functions/pci_epf_test/func1/vendorid | ||||
| 	# echo 0xb500 > functions/pci_epf_test/func1/deviceid | ||||
| 	# echo 16 > functions/pci_epf_test/func1/msi_interrupts | ||||
| 	# echo 8 > functions/pci_epf_test/func1/msix_interrupts | ||||
| 
 | ||||
| 1.5 Binding pci-epf-test Device to EP Controller | ||||
| 
 | ||||
|  | @ -120,7 +121,9 @@ following commands. | |||
| 
 | ||||
| 	Interrupt tests | ||||
| 
 | ||||
| 	SET IRQ TYPE TO LEGACY:         OKAY | ||||
| 	LEGACY IRQ:     NOT OKAY | ||||
| 	SET IRQ TYPE TO MSI:            OKAY | ||||
| 	MSI1:           OKAY | ||||
| 	MSI2:           OKAY | ||||
| 	MSI3:           OKAY | ||||
|  | @ -153,9 +156,30 @@ following commands. | |||
| 	MSI30:          NOT OKAY | ||||
| 	MSI31:          NOT OKAY | ||||
| 	MSI32:          NOT OKAY | ||||
| 	SET IRQ TYPE TO MSI-X:          OKAY | ||||
| 	MSI-X1:         OKAY | ||||
| 	MSI-X2:         OKAY | ||||
| 	MSI-X3:         OKAY | ||||
| 	MSI-X4:         OKAY | ||||
| 	MSI-X5:         OKAY | ||||
| 	MSI-X6:         OKAY | ||||
| 	MSI-X7:         OKAY | ||||
| 	MSI-X8:         OKAY | ||||
| 	MSI-X9:         NOT OKAY | ||||
| 	MSI-X10:        NOT OKAY | ||||
| 	MSI-X11:        NOT OKAY | ||||
| 	MSI-X12:        NOT OKAY | ||||
| 	MSI-X13:        NOT OKAY | ||||
| 	MSI-X14:        NOT OKAY | ||||
| 	MSI-X15:        NOT OKAY | ||||
| 	MSI-X16:        NOT OKAY | ||||
| 	[...] | ||||
| 	MSI-X2047:      NOT OKAY | ||||
| 	MSI-X2048:      NOT OKAY | ||||
| 
 | ||||
| 	Read Tests | ||||
| 
 | ||||
| 	SET IRQ TYPE TO MSI:            OKAY | ||||
| 	READ (      1 bytes):           OKAY | ||||
| 	READ (   1024 bytes):           OKAY | ||||
| 	READ (   1025 bytes):           OKAY | ||||
|  |  | |||
|  | @ -166,6 +166,7 @@ Code  Seq#(hex)	Include File		Comments | |||
| 'P'	all	linux/soundcard.h	conflict! | ||||
| 'P'	60-6F	sound/sscape_ioctl.h	conflict! | ||||
| 'P'	00-0F	drivers/usb/class/usblp.c	conflict! | ||||
| 'P'	01-09	drivers/misc/pci_endpoint_test.c	conflict! | ||||
| 'Q'	all	linux/soundcard.h | ||||
| 'R'	00-1F	linux/random.h		conflict! | ||||
| 'R'	01	linux/rfkill.h		conflict! | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ The PCI driver for the test device performs the following tests | |||
| 	*) verifying addresses programmed in BAR | ||||
| 	*) raise legacy IRQ | ||||
| 	*) raise MSI IRQ | ||||
| 	*) raise MSI-X IRQ | ||||
| 	*) read data | ||||
| 	*) write data | ||||
| 	*) copy data | ||||
|  | @ -25,6 +26,11 @@ ioctl | |||
|  PCITEST_LEGACY_IRQ: Tests legacy IRQ | ||||
|  PCITEST_MSI: Tests message signalled interrupts. The MSI number | ||||
| 	      to be tested should be passed as argument. | ||||
|  PCITEST_MSIX: Tests message signalled interrupts. The MSI-X number | ||||
| 	      to be tested should be passed as argument. | ||||
|  PCITEST_SET_IRQTYPE: Changes driver IRQ type configuration. The IRQ type | ||||
| 	      should be passed as argument (0: Legacy, 1:MSI, 2:MSI-X). | ||||
|  PCITEST_GET_IRQTYPE: Gets driver IRQ type configuration. | ||||
|  PCITEST_WRITE: Perform write tests. The size of the buffer should be passed | ||||
| 		as argument. | ||||
|  PCITEST_READ: Perform read tests. The size of the buffer should be passed | ||||
|  |  | |||
|  | @ -35,38 +35,45 @@ | |||
| 
 | ||||
| #include <uapi/linux/pcitest.h> | ||||
| 
 | ||||
| #define DRV_MODULE_NAME			"pci-endpoint-test" | ||||
| #define DRV_MODULE_NAME				"pci-endpoint-test" | ||||
| 
 | ||||
| #define PCI_ENDPOINT_TEST_MAGIC		0x0 | ||||
| #define IRQ_TYPE_UNDEFINED			-1 | ||||
| #define IRQ_TYPE_LEGACY				0 | ||||
| #define IRQ_TYPE_MSI				1 | ||||
| #define IRQ_TYPE_MSIX				2 | ||||
| 
 | ||||
| #define PCI_ENDPOINT_TEST_COMMAND	0x4 | ||||
| #define COMMAND_RAISE_LEGACY_IRQ	BIT(0) | ||||
| #define COMMAND_RAISE_MSI_IRQ		BIT(1) | ||||
| #define MSI_NUMBER_SHIFT		2 | ||||
| /* 6 bits for MSI number */ | ||||
| #define COMMAND_READ                    BIT(8) | ||||
| #define COMMAND_WRITE                   BIT(9) | ||||
| #define COMMAND_COPY                    BIT(10) | ||||
| #define PCI_ENDPOINT_TEST_MAGIC			0x0 | ||||
| 
 | ||||
| #define PCI_ENDPOINT_TEST_STATUS	0x8 | ||||
| #define STATUS_READ_SUCCESS             BIT(0) | ||||
| #define STATUS_READ_FAIL                BIT(1) | ||||
| #define STATUS_WRITE_SUCCESS            BIT(2) | ||||
| #define STATUS_WRITE_FAIL               BIT(3) | ||||
| #define STATUS_COPY_SUCCESS             BIT(4) | ||||
| #define STATUS_COPY_FAIL                BIT(5) | ||||
| #define STATUS_IRQ_RAISED               BIT(6) | ||||
| #define STATUS_SRC_ADDR_INVALID         BIT(7) | ||||
| #define STATUS_DST_ADDR_INVALID         BIT(8) | ||||
| #define PCI_ENDPOINT_TEST_COMMAND		0x4 | ||||
| #define COMMAND_RAISE_LEGACY_IRQ		BIT(0) | ||||
| #define COMMAND_RAISE_MSI_IRQ			BIT(1) | ||||
| #define COMMAND_RAISE_MSIX_IRQ			BIT(2) | ||||
| #define COMMAND_READ				BIT(3) | ||||
| #define COMMAND_WRITE				BIT(4) | ||||
| #define COMMAND_COPY				BIT(5) | ||||
| 
 | ||||
| #define PCI_ENDPOINT_TEST_LOWER_SRC_ADDR	0xc | ||||
| #define PCI_ENDPOINT_TEST_STATUS		0x8 | ||||
| #define STATUS_READ_SUCCESS			BIT(0) | ||||
| #define STATUS_READ_FAIL			BIT(1) | ||||
| #define STATUS_WRITE_SUCCESS			BIT(2) | ||||
| #define STATUS_WRITE_FAIL			BIT(3) | ||||
| #define STATUS_COPY_SUCCESS			BIT(4) | ||||
| #define STATUS_COPY_FAIL			BIT(5) | ||||
| #define STATUS_IRQ_RAISED			BIT(6) | ||||
| #define STATUS_SRC_ADDR_INVALID			BIT(7) | ||||
| #define STATUS_DST_ADDR_INVALID			BIT(8) | ||||
| 
 | ||||
| #define PCI_ENDPOINT_TEST_LOWER_SRC_ADDR	0x0c | ||||
| #define PCI_ENDPOINT_TEST_UPPER_SRC_ADDR	0x10 | ||||
| 
 | ||||
| #define PCI_ENDPOINT_TEST_LOWER_DST_ADDR	0x14 | ||||
| #define PCI_ENDPOINT_TEST_UPPER_DST_ADDR	0x18 | ||||
| 
 | ||||
| #define PCI_ENDPOINT_TEST_SIZE		0x1c | ||||
| #define PCI_ENDPOINT_TEST_CHECKSUM	0x20 | ||||
| #define PCI_ENDPOINT_TEST_SIZE			0x1c | ||||
| #define PCI_ENDPOINT_TEST_CHECKSUM		0x20 | ||||
| 
 | ||||
| #define PCI_ENDPOINT_TEST_IRQ_TYPE		0x24 | ||||
| #define PCI_ENDPOINT_TEST_IRQ_NUMBER		0x28 | ||||
| 
 | ||||
| static DEFINE_IDA(pci_endpoint_test_ida); | ||||
| 
 | ||||
|  | @ -77,6 +84,10 @@ static bool no_msi; | |||
| module_param(no_msi, bool, 0444); | ||||
| MODULE_PARM_DESC(no_msi, "Disable MSI interrupt in pci_endpoint_test"); | ||||
| 
 | ||||
| static int irq_type = IRQ_TYPE_MSI; | ||||
| module_param(irq_type, int, 0444); | ||||
| MODULE_PARM_DESC(irq_type, "IRQ mode selection in pci_endpoint_test (0 - Legacy, 1 - MSI, 2 - MSI-X)"); | ||||
| 
 | ||||
| enum pci_barno { | ||||
| 	BAR_0, | ||||
| 	BAR_1, | ||||
|  | @ -103,7 +114,7 @@ struct pci_endpoint_test { | |||
| struct pci_endpoint_test_data { | ||||
| 	enum pci_barno test_reg_bar; | ||||
| 	size_t alignment; | ||||
| 	bool no_msi; | ||||
| 	int irq_type; | ||||
| }; | ||||
| 
 | ||||
| static inline u32 pci_endpoint_test_readl(struct pci_endpoint_test *test, | ||||
|  | @ -147,6 +158,100 @@ static irqreturn_t pci_endpoint_test_irqhandler(int irq, void *dev_id) | |||
| 	return IRQ_HANDLED; | ||||
| } | ||||
| 
 | ||||
| static void pci_endpoint_test_free_irq_vectors(struct pci_endpoint_test *test) | ||||
| { | ||||
| 	struct pci_dev *pdev = test->pdev; | ||||
| 
 | ||||
| 	pci_free_irq_vectors(pdev); | ||||
| } | ||||
| 
 | ||||
| static bool pci_endpoint_test_alloc_irq_vectors(struct pci_endpoint_test *test, | ||||
| 						int type) | ||||
| { | ||||
| 	int irq = -1; | ||||
| 	struct pci_dev *pdev = test->pdev; | ||||
| 	struct device *dev = &pdev->dev; | ||||
| 	bool res = true; | ||||
| 
 | ||||
| 	switch (type) { | ||||
| 	case IRQ_TYPE_LEGACY: | ||||
| 		irq = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_LEGACY); | ||||
| 		if (irq < 0) | ||||
| 			dev_err(dev, "Failed to get Legacy interrupt\n"); | ||||
| 		break; | ||||
| 	case IRQ_TYPE_MSI: | ||||
| 		irq = pci_alloc_irq_vectors(pdev, 1, 32, PCI_IRQ_MSI); | ||||
| 		if (irq < 0) | ||||
| 			dev_err(dev, "Failed to get MSI interrupts\n"); | ||||
| 		break; | ||||
| 	case IRQ_TYPE_MSIX: | ||||
| 		irq = pci_alloc_irq_vectors(pdev, 1, 2048, PCI_IRQ_MSIX); | ||||
| 		if (irq < 0) | ||||
| 			dev_err(dev, "Failed to get MSI-X interrupts\n"); | ||||
| 		break; | ||||
| 	default: | ||||
| 		dev_err(dev, "Invalid IRQ type selected\n"); | ||||
| 	} | ||||
| 
 | ||||
| 	if (irq < 0) { | ||||
| 		irq = 0; | ||||
| 		res = false; | ||||
| 	} | ||||
| 	test->num_irqs = irq; | ||||
| 
 | ||||
| 	return res; | ||||
| } | ||||
| 
 | ||||
| static void pci_endpoint_test_release_irq(struct pci_endpoint_test *test) | ||||
| { | ||||
| 	int i; | ||||
| 	struct pci_dev *pdev = test->pdev; | ||||
| 	struct device *dev = &pdev->dev; | ||||
| 
 | ||||
| 	for (i = 0; i < test->num_irqs; i++) | ||||
| 		devm_free_irq(dev, pci_irq_vector(pdev, i), test); | ||||
| 
 | ||||
| 	test->num_irqs = 0; | ||||
| } | ||||
| 
 | ||||
| static bool pci_endpoint_test_request_irq(struct pci_endpoint_test *test) | ||||
| { | ||||
| 	int i; | ||||
| 	int err; | ||||
| 	struct pci_dev *pdev = test->pdev; | ||||
| 	struct device *dev = &pdev->dev; | ||||
| 
 | ||||
| 	for (i = 0; i < test->num_irqs; i++) { | ||||
| 		err = devm_request_irq(dev, pci_irq_vector(pdev, i), | ||||
| 				       pci_endpoint_test_irqhandler, | ||||
| 				       IRQF_SHARED, DRV_MODULE_NAME, test); | ||||
| 		if (err) | ||||
| 			goto fail; | ||||
| 	} | ||||
| 
 | ||||
| 	return true; | ||||
| 
 | ||||
| fail: | ||||
| 	switch (irq_type) { | ||||
| 	case IRQ_TYPE_LEGACY: | ||||
| 		dev_err(dev, "Failed to request IRQ %d for Legacy\n", | ||||
| 			pci_irq_vector(pdev, i)); | ||||
| 		break; | ||||
| 	case IRQ_TYPE_MSI: | ||||
| 		dev_err(dev, "Failed to request IRQ %d for MSI %d\n", | ||||
| 			pci_irq_vector(pdev, i), | ||||
| 			i + 1); | ||||
| 		break; | ||||
| 	case IRQ_TYPE_MSIX: | ||||
| 		dev_err(dev, "Failed to request IRQ %d for MSI-X %d\n", | ||||
| 			pci_irq_vector(pdev, i), | ||||
| 			i + 1); | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| static bool pci_endpoint_test_bar(struct pci_endpoint_test *test, | ||||
| 				  enum pci_barno barno) | ||||
| { | ||||
|  | @ -179,6 +284,9 @@ static bool pci_endpoint_test_legacy_irq(struct pci_endpoint_test *test) | |||
| { | ||||
| 	u32 val; | ||||
| 
 | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE, | ||||
| 				 IRQ_TYPE_LEGACY); | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 0); | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND, | ||||
| 				 COMMAND_RAISE_LEGACY_IRQ); | ||||
| 	val = wait_for_completion_timeout(&test->irq_raised, | ||||
|  | @ -190,14 +298,18 @@ static bool pci_endpoint_test_legacy_irq(struct pci_endpoint_test *test) | |||
| } | ||||
| 
 | ||||
| static bool pci_endpoint_test_msi_irq(struct pci_endpoint_test *test, | ||||
| 				      u8 msi_num) | ||||
| 				       u16 msi_num, bool msix) | ||||
| { | ||||
| 	u32 val; | ||||
| 	struct pci_dev *pdev = test->pdev; | ||||
| 
 | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE, | ||||
| 				 msix == false ? IRQ_TYPE_MSI : | ||||
| 				 IRQ_TYPE_MSIX); | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, msi_num); | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND, | ||||
| 				 msi_num << MSI_NUMBER_SHIFT | | ||||
| 				 COMMAND_RAISE_MSI_IRQ); | ||||
| 				 msix == false ? COMMAND_RAISE_MSI_IRQ : | ||||
| 				 COMMAND_RAISE_MSIX_IRQ); | ||||
| 	val = wait_for_completion_timeout(&test->irq_raised, | ||||
| 					  msecs_to_jiffies(1000)); | ||||
| 	if (!val) | ||||
|  | @ -230,6 +342,11 @@ static bool pci_endpoint_test_copy(struct pci_endpoint_test *test, size_t size) | |||
| 	if (size > SIZE_MAX - alignment) | ||||
| 		goto err; | ||||
| 
 | ||||
| 	if (irq_type < IRQ_TYPE_LEGACY || irq_type > IRQ_TYPE_MSIX) { | ||||
| 		dev_err(dev, "Invalid IRQ type option\n"); | ||||
| 		goto err; | ||||
| 	} | ||||
| 
 | ||||
| 	orig_src_addr = dma_alloc_coherent(dev, size + alignment, | ||||
| 					   &orig_src_phys_addr, GFP_KERNEL); | ||||
| 	if (!orig_src_addr) { | ||||
|  | @ -281,8 +398,10 @@ static bool pci_endpoint_test_copy(struct pci_endpoint_test *test, size_t size) | |||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_SIZE, | ||||
| 				 size); | ||||
| 
 | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE, irq_type); | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 1); | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND, | ||||
| 				 1 << MSI_NUMBER_SHIFT | COMMAND_COPY); | ||||
| 				 COMMAND_COPY); | ||||
| 
 | ||||
| 	wait_for_completion(&test->irq_raised); | ||||
| 
 | ||||
|  | @ -318,6 +437,11 @@ static bool pci_endpoint_test_write(struct pci_endpoint_test *test, size_t size) | |||
| 	if (size > SIZE_MAX - alignment) | ||||
| 		goto err; | ||||
| 
 | ||||
| 	if (irq_type < IRQ_TYPE_LEGACY || irq_type > IRQ_TYPE_MSIX) { | ||||
| 		dev_err(dev, "Invalid IRQ type option\n"); | ||||
| 		goto err; | ||||
| 	} | ||||
| 
 | ||||
| 	orig_addr = dma_alloc_coherent(dev, size + alignment, &orig_phys_addr, | ||||
| 				       GFP_KERNEL); | ||||
| 	if (!orig_addr) { | ||||
|  | @ -348,8 +472,10 @@ static bool pci_endpoint_test_write(struct pci_endpoint_test *test, size_t size) | |||
| 
 | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_SIZE, size); | ||||
| 
 | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE, irq_type); | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 1); | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND, | ||||
| 				 1 << MSI_NUMBER_SHIFT | COMMAND_READ); | ||||
| 				 COMMAND_READ); | ||||
| 
 | ||||
| 	wait_for_completion(&test->irq_raised); | ||||
| 
 | ||||
|  | @ -379,6 +505,11 @@ static bool pci_endpoint_test_read(struct pci_endpoint_test *test, size_t size) | |||
| 	if (size > SIZE_MAX - alignment) | ||||
| 		goto err; | ||||
| 
 | ||||
| 	if (irq_type < IRQ_TYPE_LEGACY || irq_type > IRQ_TYPE_MSIX) { | ||||
| 		dev_err(dev, "Invalid IRQ type option\n"); | ||||
| 		goto err; | ||||
| 	} | ||||
| 
 | ||||
| 	orig_addr = dma_alloc_coherent(dev, size + alignment, &orig_phys_addr, | ||||
| 				       GFP_KERNEL); | ||||
| 	if (!orig_addr) { | ||||
|  | @ -403,8 +534,10 @@ static bool pci_endpoint_test_read(struct pci_endpoint_test *test, size_t size) | |||
| 
 | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_SIZE, size); | ||||
| 
 | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE, irq_type); | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 1); | ||||
| 	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND, | ||||
| 				 1 << MSI_NUMBER_SHIFT | COMMAND_WRITE); | ||||
| 				 COMMAND_WRITE); | ||||
| 
 | ||||
| 	wait_for_completion(&test->irq_raised); | ||||
| 
 | ||||
|  | @ -417,6 +550,38 @@ err: | |||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static bool pci_endpoint_test_set_irq(struct pci_endpoint_test *test, | ||||
| 				      int req_irq_type) | ||||
| { | ||||
| 	struct pci_dev *pdev = test->pdev; | ||||
| 	struct device *dev = &pdev->dev; | ||||
| 
 | ||||
| 	if (req_irq_type < IRQ_TYPE_LEGACY || req_irq_type > IRQ_TYPE_MSIX) { | ||||
| 		dev_err(dev, "Invalid IRQ type option\n"); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	if (irq_type == req_irq_type) | ||||
| 		return true; | ||||
| 
 | ||||
| 	pci_endpoint_test_release_irq(test); | ||||
| 	pci_endpoint_test_free_irq_vectors(test); | ||||
| 
 | ||||
| 	if (!pci_endpoint_test_alloc_irq_vectors(test, req_irq_type)) | ||||
| 		goto err; | ||||
| 
 | ||||
| 	if (!pci_endpoint_test_request_irq(test)) | ||||
| 		goto err; | ||||
| 
 | ||||
| 	irq_type = req_irq_type; | ||||
| 	return true; | ||||
| 
 | ||||
| err: | ||||
| 	pci_endpoint_test_free_irq_vectors(test); | ||||
| 	irq_type = IRQ_TYPE_UNDEFINED; | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| static long pci_endpoint_test_ioctl(struct file *file, unsigned int cmd, | ||||
| 				    unsigned long arg) | ||||
| { | ||||
|  | @ -436,7 +601,8 @@ static long pci_endpoint_test_ioctl(struct file *file, unsigned int cmd, | |||
| 		ret = pci_endpoint_test_legacy_irq(test); | ||||
| 		break; | ||||
| 	case PCITEST_MSI: | ||||
| 		ret = pci_endpoint_test_msi_irq(test, arg); | ||||
| 	case PCITEST_MSIX: | ||||
| 		ret = pci_endpoint_test_msi_irq(test, arg, cmd == PCITEST_MSIX); | ||||
| 		break; | ||||
| 	case PCITEST_WRITE: | ||||
| 		ret = pci_endpoint_test_write(test, arg); | ||||
|  | @ -447,6 +613,12 @@ static long pci_endpoint_test_ioctl(struct file *file, unsigned int cmd, | |||
| 	case PCITEST_COPY: | ||||
| 		ret = pci_endpoint_test_copy(test, arg); | ||||
| 		break; | ||||
| 	case PCITEST_SET_IRQTYPE: | ||||
| 		ret = pci_endpoint_test_set_irq(test, arg); | ||||
| 		break; | ||||
| 	case PCITEST_GET_IRQTYPE: | ||||
| 		ret = irq_type; | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| ret: | ||||
|  | @ -462,9 +634,7 @@ static const struct file_operations pci_endpoint_test_fops = { | |||
| static int pci_endpoint_test_probe(struct pci_dev *pdev, | ||||
| 				   const struct pci_device_id *ent) | ||||
| { | ||||
| 	int i; | ||||
| 	int err; | ||||
| 	int irq = 0; | ||||
| 	int id; | ||||
| 	char name[20]; | ||||
| 	enum pci_barno bar; | ||||
|  | @ -486,11 +656,14 @@ static int pci_endpoint_test_probe(struct pci_dev *pdev, | |||
| 	test->alignment = 0; | ||||
| 	test->pdev = pdev; | ||||
| 
 | ||||
| 	if (no_msi) | ||||
| 		irq_type = IRQ_TYPE_LEGACY; | ||||
| 
 | ||||
| 	data = (struct pci_endpoint_test_data *)ent->driver_data; | ||||
| 	if (data) { | ||||
| 		test_reg_bar = data->test_reg_bar; | ||||
| 		test->alignment = data->alignment; | ||||
| 		no_msi = data->no_msi; | ||||
| 		irq_type = data->irq_type; | ||||
| 	} | ||||
| 
 | ||||
| 	init_completion(&test->irq_raised); | ||||
|  | @ -510,28 +683,11 @@ static int pci_endpoint_test_probe(struct pci_dev *pdev, | |||
| 
 | ||||
| 	pci_set_master(pdev); | ||||
| 
 | ||||
| 	if (!no_msi) { | ||||
| 		irq = pci_alloc_irq_vectors(pdev, 1, 32, PCI_IRQ_MSI); | ||||
| 		if (irq < 0) | ||||
| 			dev_err(dev, "Failed to get MSI interrupts\n"); | ||||
| 		test->num_irqs = irq; | ||||
| 	} | ||||
| 	if (!pci_endpoint_test_alloc_irq_vectors(test, irq_type)) | ||||
| 		goto err_disable_irq; | ||||
| 
 | ||||
| 	err = devm_request_irq(dev, pdev->irq, pci_endpoint_test_irqhandler, | ||||
| 			       IRQF_SHARED, DRV_MODULE_NAME, test); | ||||
| 	if (err) { | ||||
| 		dev_err(dev, "Failed to request IRQ %d\n", pdev->irq); | ||||
| 		goto err_disable_msi; | ||||
| 	} | ||||
| 
 | ||||
| 	for (i = 1; i < irq; i++) { | ||||
| 		err = devm_request_irq(dev, pci_irq_vector(pdev, i), | ||||
| 				       pci_endpoint_test_irqhandler, | ||||
| 				       IRQF_SHARED, DRV_MODULE_NAME, test); | ||||
| 		if (err) | ||||
| 			dev_err(dev, "failed to request IRQ %d for MSI %d\n", | ||||
| 				pci_irq_vector(pdev, i), i + 1); | ||||
| 	} | ||||
| 	if (!pci_endpoint_test_request_irq(test)) | ||||
| 		goto err_disable_irq; | ||||
| 
 | ||||
| 	for (bar = BAR_0; bar <= BAR_5; bar++) { | ||||
| 		if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) { | ||||
|  | @ -590,12 +746,10 @@ err_iounmap: | |||
| 		if (test->bar[bar]) | ||||
| 			pci_iounmap(pdev, test->bar[bar]); | ||||
| 	} | ||||
| 	pci_endpoint_test_release_irq(test); | ||||
| 
 | ||||
| 	for (i = 0; i < irq; i++) | ||||
| 		devm_free_irq(&pdev->dev, pci_irq_vector(pdev, i), test); | ||||
| 
 | ||||
| err_disable_msi: | ||||
| 	pci_disable_msi(pdev); | ||||
| err_disable_irq: | ||||
| 	pci_endpoint_test_free_irq_vectors(test); | ||||
| 	pci_release_regions(pdev); | ||||
| 
 | ||||
| err_disable_pdev: | ||||
|  | @ -607,7 +761,6 @@ err_disable_pdev: | |||
| static void pci_endpoint_test_remove(struct pci_dev *pdev) | ||||
| { | ||||
| 	int id; | ||||
| 	int i; | ||||
| 	enum pci_barno bar; | ||||
| 	struct pci_endpoint_test *test = pci_get_drvdata(pdev); | ||||
| 	struct miscdevice *misc_device = &test->miscdev; | ||||
|  | @ -624,9 +777,10 @@ static void pci_endpoint_test_remove(struct pci_dev *pdev) | |||
| 		if (test->bar[bar]) | ||||
| 			pci_iounmap(pdev, test->bar[bar]); | ||||
| 	} | ||||
| 	for (i = 0; i < test->num_irqs; i++) | ||||
| 		devm_free_irq(&pdev->dev, pci_irq_vector(pdev, i), test); | ||||
| 	pci_disable_msi(pdev); | ||||
| 
 | ||||
| 	pci_endpoint_test_release_irq(test); | ||||
| 	pci_endpoint_test_free_irq_vectors(test); | ||||
| 
 | ||||
| 	pci_release_regions(pdev); | ||||
| 	pci_disable_device(pdev); | ||||
| } | ||||
|  |  | |||
|  | @ -370,7 +370,7 @@ static void dra7xx_pcie_raise_msi_irq(struct dra7xx_pcie *dra7xx, | |||
| } | ||||
| 
 | ||||
| static int dra7xx_pcie_raise_irq(struct dw_pcie_ep *ep, u8 func_no, | ||||
| 				 enum pci_epc_irq_type type, u8 interrupt_num) | ||||
| 				 enum pci_epc_irq_type type, u16 interrupt_num) | ||||
| { | ||||
| 	struct dw_pcie *pci = to_dw_pcie_from_ep(ep); | ||||
| 	struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci); | ||||
|  |  | |||
|  | @ -421,7 +421,6 @@ static int __init exynos_add_pcie_port(struct exynos_pcie *ep, | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pp->root_bus_nr = -1; | ||||
| 	pp->ops = &exynos_pcie_host_ops; | ||||
| 
 | ||||
| 	ret = dw_pcie_host_init(pp); | ||||
|  |  | |||
|  | @ -667,7 +667,6 @@ static int imx6_add_pcie_port(struct imx6_pcie *imx6_pcie, | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pp->root_bus_nr = -1; | ||||
| 	pp->ops = &imx6_pcie_host_ops; | ||||
| 
 | ||||
| 	ret = dw_pcie_host_init(pp); | ||||
|  |  | |||
|  | @ -347,7 +347,6 @@ static int __init ks_add_pcie_port(struct keystone_pcie *ks_pcie, | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pp->root_bus_nr = -1; | ||||
| 	pp->ops = &keystone_pcie_host_ops; | ||||
| 	ret = ks_dw_pcie_host_init(ks_pcie, ks_pcie->msi_intc_np); | ||||
| 	if (ret) { | ||||
|  |  | |||
|  | @ -172,7 +172,6 @@ static int armada8k_add_pcie_port(struct armada8k_pcie *pcie, | |||
| 	struct device *dev = &pdev->dev; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	pp->root_bus_nr = -1; | ||||
| 	pp->ops = &armada8k_pcie_host_ops; | ||||
| 
 | ||||
| 	pp->irq = platform_get_irq(pdev, 0); | ||||
|  |  | |||
|  | @ -399,7 +399,6 @@ static int artpec6_add_pcie_port(struct artpec6_pcie *artpec6_pcie, | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pp->root_bus_nr = -1; | ||||
| 	pp->ops = &artpec6_pcie_host_ops; | ||||
| 
 | ||||
| 	ret = dw_pcie_host_init(pp); | ||||
|  | @ -428,7 +427,7 @@ static void artpec6_pcie_ep_init(struct dw_pcie_ep *ep) | |||
| } | ||||
| 
 | ||||
| static int artpec6_pcie_raise_irq(struct dw_pcie_ep *ep, u8 func_no, | ||||
| 				  enum pci_epc_irq_type type, u8 interrupt_num) | ||||
| 				  enum pci_epc_irq_type type, u16 interrupt_num) | ||||
| { | ||||
| 	struct dw_pcie *pci = to_dw_pcie_from_ep(ep); | ||||
| 
 | ||||
|  |  | |||
|  | @ -40,6 +40,39 @@ void dw_pcie_ep_reset_bar(struct dw_pcie *pci, enum pci_barno bar) | |||
| 	__dw_pcie_ep_reset_bar(pci, bar, 0); | ||||
| } | ||||
| 
 | ||||
| static u8 __dw_pcie_ep_find_next_cap(struct dw_pcie *pci, u8 cap_ptr, | ||||
| 			      u8 cap) | ||||
| { | ||||
| 	u8 cap_id, next_cap_ptr; | ||||
| 	u16 reg; | ||||
| 
 | ||||
| 	reg = dw_pcie_readw_dbi(pci, cap_ptr); | ||||
| 	next_cap_ptr = (reg & 0xff00) >> 8; | ||||
| 	cap_id = (reg & 0x00ff); | ||||
| 
 | ||||
| 	if (!next_cap_ptr || cap_id > PCI_CAP_ID_MAX) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	if (cap_id == cap) | ||||
| 		return cap_ptr; | ||||
| 
 | ||||
| 	return __dw_pcie_ep_find_next_cap(pci, next_cap_ptr, cap); | ||||
| } | ||||
| 
 | ||||
| static u8 dw_pcie_ep_find_capability(struct dw_pcie *pci, u8 cap) | ||||
| { | ||||
| 	u8 next_cap_ptr; | ||||
| 	u16 reg; | ||||
| 
 | ||||
| 	reg = dw_pcie_readw_dbi(pci, PCI_CAPABILITY_LIST); | ||||
| 	next_cap_ptr = (reg & 0x00ff); | ||||
| 
 | ||||
| 	if (!next_cap_ptr) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	return __dw_pcie_ep_find_next_cap(pci, next_cap_ptr, cap); | ||||
| } | ||||
| 
 | ||||
| static int dw_pcie_ep_write_header(struct pci_epc *epc, u8 func_no, | ||||
| 				   struct pci_epf_header *hdr) | ||||
| { | ||||
|  | @ -213,36 +246,84 @@ static int dw_pcie_ep_map_addr(struct pci_epc *epc, u8 func_no, | |||
| 
 | ||||
| static int dw_pcie_ep_get_msi(struct pci_epc *epc, u8 func_no) | ||||
| { | ||||
| 	int val; | ||||
| 	struct dw_pcie_ep *ep = epc_get_drvdata(epc); | ||||
| 	struct dw_pcie *pci = to_dw_pcie_from_ep(ep); | ||||
| 	u32 val, reg; | ||||
| 
 | ||||
| 	val = dw_pcie_readw_dbi(pci, MSI_MESSAGE_CONTROL); | ||||
| 	if (!(val & MSI_CAP_MSI_EN_MASK)) | ||||
| 	if (!ep->msi_cap) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	val = (val & MSI_CAP_MME_MASK) >> MSI_CAP_MME_SHIFT; | ||||
| 	reg = ep->msi_cap + PCI_MSI_FLAGS; | ||||
| 	val = dw_pcie_readw_dbi(pci, reg); | ||||
| 	if (!(val & PCI_MSI_FLAGS_ENABLE)) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	val = (val & PCI_MSI_FLAGS_QSIZE) >> 4; | ||||
| 
 | ||||
| 	return val; | ||||
| } | ||||
| 
 | ||||
| static int dw_pcie_ep_set_msi(struct pci_epc *epc, u8 func_no, u8 encode_int) | ||||
| static int dw_pcie_ep_set_msi(struct pci_epc *epc, u8 func_no, u8 interrupts) | ||||
| { | ||||
| 	int val; | ||||
| 	struct dw_pcie_ep *ep = epc_get_drvdata(epc); | ||||
| 	struct dw_pcie *pci = to_dw_pcie_from_ep(ep); | ||||
| 	u32 val, reg; | ||||
| 
 | ||||
| 	val = dw_pcie_readw_dbi(pci, MSI_MESSAGE_CONTROL); | ||||
| 	val &= ~MSI_CAP_MMC_MASK; | ||||
| 	val |= (encode_int << MSI_CAP_MMC_SHIFT) & MSI_CAP_MMC_MASK; | ||||
| 	if (!ep->msi_cap) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	reg = ep->msi_cap + PCI_MSI_FLAGS; | ||||
| 	val = dw_pcie_readw_dbi(pci, reg); | ||||
| 	val &= ~PCI_MSI_FLAGS_QMASK; | ||||
| 	val |= (interrupts << 1) & PCI_MSI_FLAGS_QMASK; | ||||
| 	dw_pcie_dbi_ro_wr_en(pci); | ||||
| 	dw_pcie_writew_dbi(pci, MSI_MESSAGE_CONTROL, val); | ||||
| 	dw_pcie_writew_dbi(pci, reg, val); | ||||
| 	dw_pcie_dbi_ro_wr_dis(pci); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int dw_pcie_ep_get_msix(struct pci_epc *epc, u8 func_no) | ||||
| { | ||||
| 	struct dw_pcie_ep *ep = epc_get_drvdata(epc); | ||||
| 	struct dw_pcie *pci = to_dw_pcie_from_ep(ep); | ||||
| 	u32 val, reg; | ||||
| 
 | ||||
| 	if (!ep->msix_cap) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	reg = ep->msix_cap + PCI_MSIX_FLAGS; | ||||
| 	val = dw_pcie_readw_dbi(pci, reg); | ||||
| 	if (!(val & PCI_MSIX_FLAGS_ENABLE)) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	val &= PCI_MSIX_FLAGS_QSIZE; | ||||
| 
 | ||||
| 	return val; | ||||
| } | ||||
| 
 | ||||
| static int dw_pcie_ep_set_msix(struct pci_epc *epc, u8 func_no, u16 interrupts) | ||||
| { | ||||
| 	struct dw_pcie_ep *ep = epc_get_drvdata(epc); | ||||
| 	struct dw_pcie *pci = to_dw_pcie_from_ep(ep); | ||||
| 	u32 val, reg; | ||||
| 
 | ||||
| 	if (!ep->msix_cap) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	reg = ep->msix_cap + PCI_MSIX_FLAGS; | ||||
| 	val = dw_pcie_readw_dbi(pci, reg); | ||||
| 	val &= ~PCI_MSIX_FLAGS_QSIZE; | ||||
| 	val |= interrupts; | ||||
| 	dw_pcie_dbi_ro_wr_en(pci); | ||||
| 	dw_pcie_writew_dbi(pci, reg, val); | ||||
| 	dw_pcie_dbi_ro_wr_dis(pci); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int dw_pcie_ep_raise_irq(struct pci_epc *epc, u8 func_no, | ||||
| 				enum pci_epc_irq_type type, u8 interrupt_num) | ||||
| 				enum pci_epc_irq_type type, u16 interrupt_num) | ||||
| { | ||||
| 	struct dw_pcie_ep *ep = epc_get_drvdata(epc); | ||||
| 
 | ||||
|  | @ -282,32 +363,52 @@ static const struct pci_epc_ops epc_ops = { | |||
| 	.unmap_addr		= dw_pcie_ep_unmap_addr, | ||||
| 	.set_msi		= dw_pcie_ep_set_msi, | ||||
| 	.get_msi		= dw_pcie_ep_get_msi, | ||||
| 	.set_msix		= dw_pcie_ep_set_msix, | ||||
| 	.get_msix		= dw_pcie_ep_get_msix, | ||||
| 	.raise_irq		= dw_pcie_ep_raise_irq, | ||||
| 	.start			= dw_pcie_ep_start, | ||||
| 	.stop			= dw_pcie_ep_stop, | ||||
| }; | ||||
| 
 | ||||
| int dw_pcie_ep_raise_legacy_irq(struct dw_pcie_ep *ep, u8 func_no) | ||||
| { | ||||
| 	struct dw_pcie *pci = to_dw_pcie_from_ep(ep); | ||||
| 	struct device *dev = pci->dev; | ||||
| 
 | ||||
| 	dev_err(dev, "EP cannot trigger legacy IRQs\n"); | ||||
| 
 | ||||
| 	return -EINVAL; | ||||
| } | ||||
| 
 | ||||
| int dw_pcie_ep_raise_msi_irq(struct dw_pcie_ep *ep, u8 func_no, | ||||
| 			     u8 interrupt_num) | ||||
| { | ||||
| 	struct dw_pcie *pci = to_dw_pcie_from_ep(ep); | ||||
| 	struct pci_epc *epc = ep->epc; | ||||
| 	u16 msg_ctrl, msg_data; | ||||
| 	u32 msg_addr_lower, msg_addr_upper; | ||||
| 	u32 msg_addr_lower, msg_addr_upper, reg; | ||||
| 	u64 msg_addr; | ||||
| 	bool has_upper; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	if (!ep->msi_cap) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	/* Raise MSI per the PCI Local Bus Specification Revision 3.0, 6.8.1. */ | ||||
| 	msg_ctrl = dw_pcie_readw_dbi(pci, MSI_MESSAGE_CONTROL); | ||||
| 	reg = ep->msi_cap + PCI_MSI_FLAGS; | ||||
| 	msg_ctrl = dw_pcie_readw_dbi(pci, reg); | ||||
| 	has_upper = !!(msg_ctrl & PCI_MSI_FLAGS_64BIT); | ||||
| 	msg_addr_lower = dw_pcie_readl_dbi(pci, MSI_MESSAGE_ADDR_L32); | ||||
| 	reg = ep->msi_cap + PCI_MSI_ADDRESS_LO; | ||||
| 	msg_addr_lower = dw_pcie_readl_dbi(pci, reg); | ||||
| 	if (has_upper) { | ||||
| 		msg_addr_upper = dw_pcie_readl_dbi(pci, MSI_MESSAGE_ADDR_U32); | ||||
| 		msg_data = dw_pcie_readw_dbi(pci, MSI_MESSAGE_DATA_64); | ||||
| 		reg = ep->msi_cap + PCI_MSI_ADDRESS_HI; | ||||
| 		msg_addr_upper = dw_pcie_readl_dbi(pci, reg); | ||||
| 		reg = ep->msi_cap + PCI_MSI_DATA_64; | ||||
| 		msg_data = dw_pcie_readw_dbi(pci, reg); | ||||
| 	} else { | ||||
| 		msg_addr_upper = 0; | ||||
| 		msg_data = dw_pcie_readw_dbi(pci, MSI_MESSAGE_DATA_32); | ||||
| 		reg = ep->msi_cap + PCI_MSI_DATA_32; | ||||
| 		msg_data = dw_pcie_readw_dbi(pci, reg); | ||||
| 	} | ||||
| 	msg_addr = ((u64) msg_addr_upper) << 32 | msg_addr_lower; | ||||
| 	ret = dw_pcie_ep_map_addr(epc, func_no, ep->msi_mem_phys, msg_addr, | ||||
|  | @ -322,6 +423,64 @@ int dw_pcie_ep_raise_msi_irq(struct dw_pcie_ep *ep, u8 func_no, | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| int dw_pcie_ep_raise_msix_irq(struct dw_pcie_ep *ep, u8 func_no, | ||||
| 			     u16 interrupt_num) | ||||
| { | ||||
| 	struct dw_pcie *pci = to_dw_pcie_from_ep(ep); | ||||
| 	struct pci_epc *epc = ep->epc; | ||||
| 	u16 tbl_offset, bir; | ||||
| 	u32 bar_addr_upper, bar_addr_lower; | ||||
| 	u32 msg_addr_upper, msg_addr_lower; | ||||
| 	u32 reg, msg_data, vec_ctrl; | ||||
| 	u64 tbl_addr, msg_addr, reg_u64; | ||||
| 	void __iomem *msix_tbl; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	reg = ep->msix_cap + PCI_MSIX_TABLE; | ||||
| 	tbl_offset = dw_pcie_readl_dbi(pci, reg); | ||||
| 	bir = (tbl_offset & PCI_MSIX_TABLE_BIR); | ||||
| 	tbl_offset &= PCI_MSIX_TABLE_OFFSET; | ||||
| 	tbl_offset >>= 3; | ||||
| 
 | ||||
| 	reg = PCI_BASE_ADDRESS_0 + (4 * bir); | ||||
| 	bar_addr_upper = 0; | ||||
| 	bar_addr_lower = dw_pcie_readl_dbi(pci, reg); | ||||
| 	reg_u64 = (bar_addr_lower & PCI_BASE_ADDRESS_MEM_TYPE_MASK); | ||||
| 	if (reg_u64 == PCI_BASE_ADDRESS_MEM_TYPE_64) | ||||
| 		bar_addr_upper = dw_pcie_readl_dbi(pci, reg + 4); | ||||
| 
 | ||||
| 	tbl_addr = ((u64) bar_addr_upper) << 32 | bar_addr_lower; | ||||
| 	tbl_addr += (tbl_offset + ((interrupt_num - 1) * PCI_MSIX_ENTRY_SIZE)); | ||||
| 	tbl_addr &= PCI_BASE_ADDRESS_MEM_MASK; | ||||
| 
 | ||||
| 	msix_tbl = ioremap_nocache(ep->phys_base + tbl_addr, | ||||
| 				   PCI_MSIX_ENTRY_SIZE); | ||||
| 	if (!msix_tbl) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	msg_addr_lower = readl(msix_tbl + PCI_MSIX_ENTRY_LOWER_ADDR); | ||||
| 	msg_addr_upper = readl(msix_tbl + PCI_MSIX_ENTRY_UPPER_ADDR); | ||||
| 	msg_addr = ((u64) msg_addr_upper) << 32 | msg_addr_lower; | ||||
| 	msg_data = readl(msix_tbl + PCI_MSIX_ENTRY_DATA); | ||||
| 	vec_ctrl = readl(msix_tbl + PCI_MSIX_ENTRY_VECTOR_CTRL); | ||||
| 
 | ||||
| 	iounmap(msix_tbl); | ||||
| 
 | ||||
| 	if (vec_ctrl & PCI_MSIX_ENTRY_CTRL_MASKBIT) | ||||
| 		return -EPERM; | ||||
| 
 | ||||
| 	ret = dw_pcie_ep_map_addr(epc, func_no, ep->msi_mem_phys, msg_addr, | ||||
| 				  epc->mem->page_size); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	writel(msg_data, ep->msi_mem); | ||||
| 
 | ||||
| 	dw_pcie_ep_unmap_addr(epc, func_no, ep->msi_mem_phys); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| void dw_pcie_ep_exit(struct dw_pcie_ep *ep) | ||||
| { | ||||
| 	struct pci_epc *epc = ep->epc; | ||||
|  | @ -386,15 +545,18 @@ int dw_pcie_ep_init(struct dw_pcie_ep *ep) | |||
| 		return -ENOMEM; | ||||
| 	ep->outbound_addr = addr; | ||||
| 
 | ||||
| 	if (ep->ops->ep_init) | ||||
| 		ep->ops->ep_init(ep); | ||||
| 
 | ||||
| 	epc = devm_pci_epc_create(dev, &epc_ops); | ||||
| 	if (IS_ERR(epc)) { | ||||
| 		dev_err(dev, "Failed to create epc device\n"); | ||||
| 		return PTR_ERR(epc); | ||||
| 	} | ||||
| 
 | ||||
| 	ep->epc = epc; | ||||
| 	epc_set_drvdata(epc, ep); | ||||
| 
 | ||||
| 	if (ep->ops->ep_init) | ||||
| 		ep->ops->ep_init(ep); | ||||
| 
 | ||||
| 	ret = of_property_read_u8(np, "max-functions", &epc->max_functions); | ||||
| 	if (ret < 0) | ||||
| 		epc->max_functions = 1; | ||||
|  | @ -409,15 +571,13 @@ int dw_pcie_ep_init(struct dw_pcie_ep *ep) | |||
| 	ep->msi_mem = pci_epc_mem_alloc_addr(epc, &ep->msi_mem_phys, | ||||
| 					     epc->mem->page_size); | ||||
| 	if (!ep->msi_mem) { | ||||
| 		dev_err(dev, "Failed to reserve memory for MSI\n"); | ||||
| 		dev_err(dev, "Failed to reserve memory for MSI/MSI-X\n"); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
| 	ep->msi_cap = dw_pcie_ep_find_capability(pci, PCI_CAP_ID_MSI); | ||||
| 
 | ||||
| 	epc->features = EPC_FEATURE_NO_LINKUP_NOTIFIER; | ||||
| 	EPC_FEATURE_SET_BAR(epc->features, BAR_0); | ||||
| 	ep->msix_cap = dw_pcie_ep_find_capability(pci, PCI_CAP_ID_MSIX); | ||||
| 
 | ||||
| 	ep->epc = epc; | ||||
| 	epc_set_drvdata(epc, ep); | ||||
| 	dw_pcie_setup(pci); | ||||
| 
 | ||||
| 	return 0; | ||||
|  |  | |||
|  | @ -70,24 +70,29 @@ static const struct dw_pcie_ops dw_pcie_ops = { | |||
| static void dw_plat_pcie_ep_init(struct dw_pcie_ep *ep) | ||||
| { | ||||
| 	struct dw_pcie *pci = to_dw_pcie_from_ep(ep); | ||||
| 	struct pci_epc *epc = ep->epc; | ||||
| 	enum pci_barno bar; | ||||
| 
 | ||||
| 	for (bar = BAR_0; bar <= BAR_5; bar++) | ||||
| 		dw_pcie_ep_reset_bar(pci, bar); | ||||
| 
 | ||||
| 	epc->features |= EPC_FEATURE_NO_LINKUP_NOTIFIER; | ||||
| 	epc->features |= EPC_FEATURE_MSIX_AVAILABLE; | ||||
| } | ||||
| 
 | ||||
| static int dw_plat_pcie_ep_raise_irq(struct dw_pcie_ep *ep, u8 func_no, | ||||
| 				     enum pci_epc_irq_type type, | ||||
| 				     u8 interrupt_num) | ||||
| 				     u16 interrupt_num) | ||||
| { | ||||
| 	struct dw_pcie *pci = to_dw_pcie_from_ep(ep); | ||||
| 
 | ||||
| 	switch (type) { | ||||
| 	case PCI_EPC_IRQ_LEGACY: | ||||
| 		dev_err(pci->dev, "EP cannot trigger legacy IRQs\n"); | ||||
| 		return -EINVAL; | ||||
| 		return dw_pcie_ep_raise_legacy_irq(ep, func_no); | ||||
| 	case PCI_EPC_IRQ_MSI: | ||||
| 		return dw_pcie_ep_raise_msi_irq(ep, func_no, interrupt_num); | ||||
| 	case PCI_EPC_IRQ_MSIX: | ||||
| 		return dw_pcie_ep_raise_msix_irq(ep, func_no, interrupt_num); | ||||
| 	default: | ||||
| 		dev_err(pci->dev, "UNKNOWN IRQ type\n"); | ||||
| 	} | ||||
|  | @ -118,7 +123,6 @@ static int dw_plat_add_pcie_port(struct dw_plat_pcie *dw_plat_pcie, | |||
| 			return pp->msi_irq; | ||||
| 	} | ||||
| 
 | ||||
| 	pp->root_bus_nr = -1; | ||||
| 	pp->ops = &dw_plat_pcie_host_ops; | ||||
| 
 | ||||
| 	ret = dw_pcie_host_init(pp); | ||||
|  |  | |||
|  | @ -96,17 +96,6 @@ | |||
| #define PCIE_GET_ATU_INB_UNR_REG_OFFSET(region)				\ | ||||
| 			((0x3 << 20) | ((region) << 9) | (0x1 << 8)) | ||||
| 
 | ||||
| #define MSI_MESSAGE_CONTROL		0x52 | ||||
| #define MSI_CAP_MMC_SHIFT		1 | ||||
| #define MSI_CAP_MMC_MASK		(7 << MSI_CAP_MMC_SHIFT) | ||||
| #define MSI_CAP_MME_SHIFT		4 | ||||
| #define MSI_CAP_MSI_EN_MASK		0x1 | ||||
| #define MSI_CAP_MME_MASK		(7 << MSI_CAP_MME_SHIFT) | ||||
| #define MSI_MESSAGE_ADDR_L32		0x54 | ||||
| #define MSI_MESSAGE_ADDR_U32		0x58 | ||||
| #define MSI_MESSAGE_DATA_32		0x58 | ||||
| #define MSI_MESSAGE_DATA_64		0x5C | ||||
| 
 | ||||
| #define MAX_MSI_IRQS			256 | ||||
| #define MAX_MSI_IRQS_PER_CTRL		32 | ||||
| #define MAX_MSI_CTRLS			(MAX_MSI_IRQS / MAX_MSI_IRQS_PER_CTRL) | ||||
|  | @ -191,7 +180,7 @@ enum dw_pcie_as_type { | |||
| struct dw_pcie_ep_ops { | ||||
| 	void	(*ep_init)(struct dw_pcie_ep *ep); | ||||
| 	int	(*raise_irq)(struct dw_pcie_ep *ep, u8 func_no, | ||||
| 			     enum pci_epc_irq_type type, u8 interrupt_num); | ||||
| 			     enum pci_epc_irq_type type, u16 interrupt_num); | ||||
| }; | ||||
| 
 | ||||
| struct dw_pcie_ep { | ||||
|  | @ -208,6 +197,8 @@ struct dw_pcie_ep { | |||
| 	u32			num_ob_windows; | ||||
| 	void __iomem		*msi_mem; | ||||
| 	phys_addr_t		msi_mem_phys; | ||||
| 	u8			msi_cap;	/* MSI capability offset */ | ||||
| 	u8			msix_cap;	/* MSI-X capability offset */ | ||||
| }; | ||||
| 
 | ||||
| struct dw_pcie_ops { | ||||
|  | @ -357,8 +348,11 @@ static inline int dw_pcie_allocate_domains(struct pcie_port *pp) | |||
| void dw_pcie_ep_linkup(struct dw_pcie_ep *ep); | ||||
| int dw_pcie_ep_init(struct dw_pcie_ep *ep); | ||||
| void dw_pcie_ep_exit(struct dw_pcie_ep *ep); | ||||
| int dw_pcie_ep_raise_legacy_irq(struct dw_pcie_ep *ep, u8 func_no); | ||||
| int dw_pcie_ep_raise_msi_irq(struct dw_pcie_ep *ep, u8 func_no, | ||||
| 			     u8 interrupt_num); | ||||
| int dw_pcie_ep_raise_msix_irq(struct dw_pcie_ep *ep, u8 func_no, | ||||
| 			     u16 interrupt_num); | ||||
| void dw_pcie_ep_reset_bar(struct dw_pcie *pci, enum pci_barno bar); | ||||
| #else | ||||
| static inline void dw_pcie_ep_linkup(struct dw_pcie_ep *ep) | ||||
|  | @ -374,12 +368,23 @@ static inline void dw_pcie_ep_exit(struct dw_pcie_ep *ep) | |||
| { | ||||
| } | ||||
| 
 | ||||
| static inline int dw_pcie_ep_raise_legacy_irq(struct dw_pcie_ep *ep, u8 func_no) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static inline int dw_pcie_ep_raise_msi_irq(struct dw_pcie_ep *ep, u8 func_no, | ||||
| 					   u8 interrupt_num) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static inline int dw_pcie_ep_raise_msix_irq(struct dw_pcie_ep *ep, u8 func_no, | ||||
| 					   u16 interrupt_num) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static inline void dw_pcie_ep_reset_bar(struct dw_pcie *pci, enum pci_barno bar) | ||||
| { | ||||
| } | ||||
|  |  | |||
|  | @ -420,7 +420,6 @@ static int histb_pcie_probe(struct platform_device *pdev) | |||
| 		phy_init(hipcie->phy); | ||||
| 	} | ||||
| 
 | ||||
| 	pp->root_bus_nr = -1; | ||||
| 	pp->ops = &histb_pcie_host_ops; | ||||
| 
 | ||||
| 	platform_set_drvdata(pdev, hipcie); | ||||
|  |  | |||
|  | @ -430,6 +430,9 @@ static int kirin_pcie_host_init(struct pcie_port *pp) | |||
| { | ||||
| 	kirin_pcie_establish_link(pp); | ||||
| 
 | ||||
| 	if (IS_ENABLED(CONFIG_PCI_MSI)) | ||||
| 		dw_pcie_msi_init(pp); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -445,9 +448,34 @@ static const struct dw_pcie_host_ops kirin_pcie_host_ops = { | |||
| 	.host_init = kirin_pcie_host_init, | ||||
| }; | ||||
| 
 | ||||
| static int kirin_pcie_add_msi(struct dw_pcie *pci, | ||||
| 				struct platform_device *pdev) | ||||
| { | ||||
| 	int irq; | ||||
| 
 | ||||
| 	if (IS_ENABLED(CONFIG_PCI_MSI)) { | ||||
| 		irq = platform_get_irq(pdev, 0); | ||||
| 		if (irq < 0) { | ||||
| 			dev_err(&pdev->dev, | ||||
| 				"failed to get MSI IRQ (%d)\n", irq); | ||||
| 			return irq; | ||||
| 		} | ||||
| 
 | ||||
| 		pci->pp.msi_irq = irq; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int __init kirin_add_pcie_port(struct dw_pcie *pci, | ||||
| 				      struct platform_device *pdev) | ||||
| { | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = kirin_pcie_add_msi(pci, pdev); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	pci->pp.ops = &kirin_pcie_host_ops; | ||||
| 
 | ||||
| 	return dw_pcie_host_init(&pci->pp); | ||||
|  |  | |||
|  | @ -1251,7 +1251,6 @@ static int qcom_pcie_probe(struct platform_device *pdev) | |||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	pp->root_bus_nr = -1; | ||||
| 	pp->ops = &qcom_pcie_dw_ops; | ||||
| 
 | ||||
| 	if (IS_ENABLED(CONFIG_PCI_MSI)) { | ||||
|  |  | |||
|  | @ -210,7 +210,6 @@ static int spear13xx_add_pcie_port(struct spear13xx_pcie *spear13xx_pcie, | |||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	pp->root_bus_nr = -1; | ||||
| 	pp->ops = &spear13xx_pcie_host_ops; | ||||
| 
 | ||||
| 	ret = dw_pcie_host_init(pp); | ||||
|  |  | |||
|  | @ -362,7 +362,8 @@ static int cdns_pcie_ep_send_msi_irq(struct cdns_pcie_ep *ep, u8 fn, | |||
| } | ||||
| 
 | ||||
| static int cdns_pcie_ep_raise_irq(struct pci_epc *epc, u8 fn, | ||||
| 				  enum pci_epc_irq_type type, u8 interrupt_num) | ||||
| 				  enum pci_epc_irq_type type, | ||||
| 				  u16 interrupt_num) | ||||
| { | ||||
| 	struct cdns_pcie_ep *ep = epc_get_drvdata(epc); | ||||
| 
 | ||||
|  |  | |||
|  | @ -472,7 +472,7 @@ static int rockchip_pcie_ep_send_msi_irq(struct rockchip_pcie_ep *ep, u8 fn, | |||
| 
 | ||||
| static int rockchip_pcie_ep_raise_irq(struct pci_epc *epc, u8 fn, | ||||
| 				      enum pci_epc_irq_type type, | ||||
| 				      u8 interrupt_num) | ||||
| 				      u16 interrupt_num) | ||||
| { | ||||
| 	struct rockchip_pcie_ep *ep = epc_get_drvdata(epc); | ||||
| 
 | ||||
|  |  | |||
|  | @ -18,13 +18,16 @@ | |||
| #include <linux/pci-epf.h> | ||||
| #include <linux/pci_regs.h> | ||||
| 
 | ||||
| #define IRQ_TYPE_LEGACY			0 | ||||
| #define IRQ_TYPE_MSI			1 | ||||
| #define IRQ_TYPE_MSIX			2 | ||||
| 
 | ||||
| #define COMMAND_RAISE_LEGACY_IRQ	BIT(0) | ||||
| #define COMMAND_RAISE_MSI_IRQ		BIT(1) | ||||
| #define MSI_NUMBER_SHIFT		2 | ||||
| #define MSI_NUMBER_MASK			(0x3f << MSI_NUMBER_SHIFT) | ||||
| #define COMMAND_READ			BIT(8) | ||||
| #define COMMAND_WRITE			BIT(9) | ||||
| #define COMMAND_COPY			BIT(10) | ||||
| #define COMMAND_RAISE_MSIX_IRQ		BIT(2) | ||||
| #define COMMAND_READ			BIT(3) | ||||
| #define COMMAND_WRITE			BIT(4) | ||||
| #define COMMAND_COPY			BIT(5) | ||||
| 
 | ||||
| #define STATUS_READ_SUCCESS		BIT(0) | ||||
| #define STATUS_READ_FAIL		BIT(1) | ||||
|  | @ -45,6 +48,7 @@ struct pci_epf_test { | |||
| 	struct pci_epf		*epf; | ||||
| 	enum pci_barno		test_reg_bar; | ||||
| 	bool			linkup_notifier; | ||||
| 	bool			msix_available; | ||||
| 	struct delayed_work	cmd_handler; | ||||
| }; | ||||
| 
 | ||||
|  | @ -56,6 +60,8 @@ struct pci_epf_test_reg { | |||
| 	u64	dst_addr; | ||||
| 	u32	size; | ||||
| 	u32	checksum; | ||||
| 	u32	irq_type; | ||||
| 	u32	irq_number; | ||||
| } __packed; | ||||
| 
 | ||||
| static struct pci_epf_header test_header = { | ||||
|  | @ -244,31 +250,42 @@ err: | |||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static void pci_epf_test_raise_irq(struct pci_epf_test *epf_test, u8 irq) | ||||
| static void pci_epf_test_raise_irq(struct pci_epf_test *epf_test, u8 irq_type, | ||||
| 				   u16 irq) | ||||
| { | ||||
| 	u8 msi_count; | ||||
| 	struct pci_epf *epf = epf_test->epf; | ||||
| 	struct device *dev = &epf->dev; | ||||
| 	struct pci_epc *epc = epf->epc; | ||||
| 	enum pci_barno test_reg_bar = epf_test->test_reg_bar; | ||||
| 	struct pci_epf_test_reg *reg = epf_test->reg[test_reg_bar]; | ||||
| 
 | ||||
| 	reg->status |= STATUS_IRQ_RAISED; | ||||
| 	msi_count = pci_epc_get_msi(epc, epf->func_no); | ||||
| 	if (irq > msi_count || msi_count <= 0) | ||||
| 
 | ||||
| 	switch (irq_type) { | ||||
| 	case IRQ_TYPE_LEGACY: | ||||
| 		pci_epc_raise_irq(epc, epf->func_no, PCI_EPC_IRQ_LEGACY, 0); | ||||
| 	else | ||||
| 		break; | ||||
| 	case IRQ_TYPE_MSI: | ||||
| 		pci_epc_raise_irq(epc, epf->func_no, PCI_EPC_IRQ_MSI, irq); | ||||
| 		break; | ||||
| 	case IRQ_TYPE_MSIX: | ||||
| 		pci_epc_raise_irq(epc, epf->func_no, PCI_EPC_IRQ_MSIX, irq); | ||||
| 		break; | ||||
| 	default: | ||||
| 		dev_err(dev, "Failed to raise IRQ, unknown type\n"); | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void pci_epf_test_cmd_handler(struct work_struct *work) | ||||
| { | ||||
| 	int ret; | ||||
| 	u8 irq; | ||||
| 	u8 msi_count; | ||||
| 	int count; | ||||
| 	u32 command; | ||||
| 	struct pci_epf_test *epf_test = container_of(work, struct pci_epf_test, | ||||
| 						     cmd_handler.work); | ||||
| 	struct pci_epf *epf = epf_test->epf; | ||||
| 	struct device *dev = &epf->dev; | ||||
| 	struct pci_epc *epc = epf->epc; | ||||
| 	enum pci_barno test_reg_bar = epf_test->test_reg_bar; | ||||
| 	struct pci_epf_test_reg *reg = epf_test->reg[test_reg_bar]; | ||||
|  | @ -280,7 +297,10 @@ static void pci_epf_test_cmd_handler(struct work_struct *work) | |||
| 	reg->command = 0; | ||||
| 	reg->status = 0; | ||||
| 
 | ||||
| 	irq = (command & MSI_NUMBER_MASK) >> MSI_NUMBER_SHIFT; | ||||
| 	if (reg->irq_type > IRQ_TYPE_MSIX) { | ||||
| 		dev_err(dev, "Failed to detect IRQ type\n"); | ||||
| 		goto reset_handler; | ||||
| 	} | ||||
| 
 | ||||
| 	if (command & COMMAND_RAISE_LEGACY_IRQ) { | ||||
| 		reg->status = STATUS_IRQ_RAISED; | ||||
|  | @ -294,7 +314,8 @@ static void pci_epf_test_cmd_handler(struct work_struct *work) | |||
| 			reg->status |= STATUS_WRITE_FAIL; | ||||
| 		else | ||||
| 			reg->status |= STATUS_WRITE_SUCCESS; | ||||
| 		pci_epf_test_raise_irq(epf_test, irq); | ||||
| 		pci_epf_test_raise_irq(epf_test, reg->irq_type, | ||||
| 				       reg->irq_number); | ||||
| 		goto reset_handler; | ||||
| 	} | ||||
| 
 | ||||
|  | @ -304,7 +325,8 @@ static void pci_epf_test_cmd_handler(struct work_struct *work) | |||
| 			reg->status |= STATUS_READ_SUCCESS; | ||||
| 		else | ||||
| 			reg->status |= STATUS_READ_FAIL; | ||||
| 		pci_epf_test_raise_irq(epf_test, irq); | ||||
| 		pci_epf_test_raise_irq(epf_test, reg->irq_type, | ||||
| 				       reg->irq_number); | ||||
| 		goto reset_handler; | ||||
| 	} | ||||
| 
 | ||||
|  | @ -314,16 +336,28 @@ static void pci_epf_test_cmd_handler(struct work_struct *work) | |||
| 			reg->status |= STATUS_COPY_SUCCESS; | ||||
| 		else | ||||
| 			reg->status |= STATUS_COPY_FAIL; | ||||
| 		pci_epf_test_raise_irq(epf_test, irq); | ||||
| 		pci_epf_test_raise_irq(epf_test, reg->irq_type, | ||||
| 				       reg->irq_number); | ||||
| 		goto reset_handler; | ||||
| 	} | ||||
| 
 | ||||
| 	if (command & COMMAND_RAISE_MSI_IRQ) { | ||||
| 		msi_count = pci_epc_get_msi(epc, epf->func_no); | ||||
| 		if (irq > msi_count || msi_count <= 0) | ||||
| 		count = pci_epc_get_msi(epc, epf->func_no); | ||||
| 		if (reg->irq_number > count || count <= 0) | ||||
| 			goto reset_handler; | ||||
| 		reg->status = STATUS_IRQ_RAISED; | ||||
| 		pci_epc_raise_irq(epc, epf->func_no, PCI_EPC_IRQ_MSI, irq); | ||||
| 		pci_epc_raise_irq(epc, epf->func_no, PCI_EPC_IRQ_MSI, | ||||
| 				  reg->irq_number); | ||||
| 		goto reset_handler; | ||||
| 	} | ||||
| 
 | ||||
| 	if (command & COMMAND_RAISE_MSIX_IRQ) { | ||||
| 		count = pci_epc_get_msix(epc, epf->func_no); | ||||
| 		if (reg->irq_number > count || count <= 0) | ||||
| 			goto reset_handler; | ||||
| 		reg->status = STATUS_IRQ_RAISED; | ||||
| 		pci_epc_raise_irq(epc, epf->func_no, PCI_EPC_IRQ_MSIX, | ||||
| 				  reg->irq_number); | ||||
| 		goto reset_handler; | ||||
| 	} | ||||
| 
 | ||||
|  | @ -440,6 +474,8 @@ static int pci_epf_test_bind(struct pci_epf *epf) | |||
| 	else | ||||
| 		epf_test->linkup_notifier = true; | ||||
| 
 | ||||
| 	epf_test->msix_available = epc->features & EPC_FEATURE_MSIX_AVAILABLE; | ||||
| 
 | ||||
| 	epf_test->test_reg_bar = EPC_FEATURE_GET_BAR(epc->features); | ||||
| 
 | ||||
| 	ret = pci_epc_write_header(epc, epf->func_no, header); | ||||
|  | @ -457,8 +493,18 @@ static int pci_epf_test_bind(struct pci_epf *epf) | |||
| 		return ret; | ||||
| 
 | ||||
| 	ret = pci_epc_set_msi(epc, epf->func_no, epf->msi_interrupts); | ||||
| 	if (ret) | ||||
| 	if (ret) { | ||||
| 		dev_err(dev, "MSI configuration failed\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	if (epf_test->msix_available) { | ||||
| 		ret = pci_epc_set_msix(epc, epf->func_no, epf->msix_interrupts); | ||||
| 		if (ret) { | ||||
| 			dev_err(dev, "MSI-X configuration failed\n"); | ||||
| 			return ret; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (!epf_test->linkup_notifier) | ||||
| 		queue_work(kpcitest_workqueue, &epf_test->cmd_handler.work); | ||||
|  |  | |||
|  | @ -286,6 +286,28 @@ static ssize_t pci_epf_msi_interrupts_show(struct config_item *item, | |||
| 		       to_pci_epf_group(item)->epf->msi_interrupts); | ||||
| } | ||||
| 
 | ||||
| static ssize_t pci_epf_msix_interrupts_store(struct config_item *item, | ||||
| 					     const char *page, size_t len) | ||||
| { | ||||
| 	u16 val; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = kstrtou16(page, 0, &val); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	to_pci_epf_group(item)->epf->msix_interrupts = val; | ||||
| 
 | ||||
| 	return len; | ||||
| } | ||||
| 
 | ||||
| static ssize_t pci_epf_msix_interrupts_show(struct config_item *item, | ||||
| 					    char *page) | ||||
| { | ||||
| 	return sprintf(page, "%d\n", | ||||
| 		       to_pci_epf_group(item)->epf->msix_interrupts); | ||||
| } | ||||
| 
 | ||||
| PCI_EPF_HEADER_R(vendorid) | ||||
| PCI_EPF_HEADER_W_u16(vendorid) | ||||
| 
 | ||||
|  | @ -327,6 +349,7 @@ CONFIGFS_ATTR(pci_epf_, subsys_vendor_id); | |||
| CONFIGFS_ATTR(pci_epf_, subsys_id); | ||||
| CONFIGFS_ATTR(pci_epf_, interrupt_pin); | ||||
| CONFIGFS_ATTR(pci_epf_, msi_interrupts); | ||||
| CONFIGFS_ATTR(pci_epf_, msix_interrupts); | ||||
| 
 | ||||
| static struct configfs_attribute *pci_epf_attrs[] = { | ||||
| 	&pci_epf_attr_vendorid, | ||||
|  | @ -340,6 +363,7 @@ static struct configfs_attribute *pci_epf_attrs[] = { | |||
| 	&pci_epf_attr_subsys_id, | ||||
| 	&pci_epf_attr_interrupt_pin, | ||||
| 	&pci_epf_attr_msi_interrupts, | ||||
| 	&pci_epf_attr_msix_interrupts, | ||||
| 	NULL, | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -131,13 +131,13 @@ EXPORT_SYMBOL_GPL(pci_epc_start); | |||
|  * pci_epc_raise_irq() - interrupt the host system | ||||
|  * @epc: the EPC device which has to interrupt the host | ||||
|  * @func_no: the endpoint function number in the EPC device | ||||
|  * @type: specify the type of interrupt; legacy or MSI | ||||
|  * @interrupt_num: the MSI interrupt number | ||||
|  * @type: specify the type of interrupt; legacy, MSI or MSI-X | ||||
|  * @interrupt_num: the MSI or MSI-X interrupt number | ||||
|  * | ||||
|  * Invoke to raise an MSI or legacy interrupt | ||||
|  * Invoke to raise an legacy, MSI or MSI-X interrupt | ||||
|  */ | ||||
| int pci_epc_raise_irq(struct pci_epc *epc, u8 func_no, | ||||
| 		      enum pci_epc_irq_type type, u8 interrupt_num) | ||||
| 		      enum pci_epc_irq_type type, u16 interrupt_num) | ||||
| { | ||||
| 	int ret; | ||||
| 	unsigned long flags; | ||||
|  | @ -201,7 +201,8 @@ int pci_epc_set_msi(struct pci_epc *epc, u8 func_no, u8 interrupts) | |||
| 	u8 encode_int; | ||||
| 	unsigned long flags; | ||||
| 
 | ||||
| 	if (IS_ERR_OR_NULL(epc) || func_no >= epc->max_functions) | ||||
| 	if (IS_ERR_OR_NULL(epc) || func_no >= epc->max_functions || | ||||
| 	    interrupts > 32) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	if (!epc->ops->set_msi) | ||||
|  | @ -217,6 +218,63 @@ int pci_epc_set_msi(struct pci_epc *epc, u8 func_no, u8 interrupts) | |||
| } | ||||
| EXPORT_SYMBOL_GPL(pci_epc_set_msi); | ||||
| 
 | ||||
| /**
 | ||||
|  * pci_epc_get_msix() - get the number of MSI-X interrupt numbers allocated | ||||
|  * @epc: the EPC device to which MSI-X interrupts was requested | ||||
|  * @func_no: the endpoint function number in the EPC device | ||||
|  * | ||||
|  * Invoke to get the number of MSI-X interrupts allocated by the RC | ||||
|  */ | ||||
| int pci_epc_get_msix(struct pci_epc *epc, u8 func_no) | ||||
| { | ||||
| 	int interrupt; | ||||
| 	unsigned long flags; | ||||
| 
 | ||||
| 	if (IS_ERR_OR_NULL(epc) || func_no >= epc->max_functions) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	if (!epc->ops->get_msix) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	spin_lock_irqsave(&epc->lock, flags); | ||||
| 	interrupt = epc->ops->get_msix(epc, func_no); | ||||
| 	spin_unlock_irqrestore(&epc->lock, flags); | ||||
| 
 | ||||
| 	if (interrupt < 0) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	return interrupt + 1; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(pci_epc_get_msix); | ||||
| 
 | ||||
| /**
 | ||||
|  * pci_epc_set_msix() - set the number of MSI-X interrupt numbers required | ||||
|  * @epc: the EPC device on which MSI-X has to be configured | ||||
|  * @func_no: the endpoint function number in the EPC device | ||||
|  * @interrupts: number of MSI-X interrupts required by the EPF | ||||
|  * | ||||
|  * Invoke to set the required number of MSI-X interrupts. | ||||
|  */ | ||||
| int pci_epc_set_msix(struct pci_epc *epc, u8 func_no, u16 interrupts) | ||||
| { | ||||
| 	int ret; | ||||
| 	unsigned long flags; | ||||
| 
 | ||||
| 	if (IS_ERR_OR_NULL(epc) || func_no >= epc->max_functions || | ||||
| 	    interrupts < 1 || interrupts > 2048) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	if (!epc->ops->set_msix) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	spin_lock_irqsave(&epc->lock, flags); | ||||
| 	ret = epc->ops->set_msix(epc, func_no, interrupts - 1); | ||||
| 	spin_unlock_irqrestore(&epc->lock, flags); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(pci_epc_set_msix); | ||||
| 
 | ||||
| /**
 | ||||
|  * pci_epc_unmap_addr() - unmap CPU address from PCI address | ||||
|  * @epc: the EPC device on which address is allocated | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ enum pci_epc_irq_type { | |||
| 	PCI_EPC_IRQ_UNKNOWN, | ||||
| 	PCI_EPC_IRQ_LEGACY, | ||||
| 	PCI_EPC_IRQ_MSI, | ||||
| 	PCI_EPC_IRQ_MSIX, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -30,7 +31,11 @@ enum pci_epc_irq_type { | |||
|  *	     capability register | ||||
|  * @get_msi: ops to get the number of MSI interrupts allocated by the RC from | ||||
|  *	     the MSI capability register | ||||
|  * @raise_irq: ops to raise a legacy or MSI interrupt | ||||
|  * @set_msix: ops to set the requested number of MSI-X interrupts in the | ||||
|  *	     MSI-X capability register | ||||
|  * @get_msix: ops to get the number of MSI-X interrupts allocated by the RC | ||||
|  *	     from the MSI-X capability register | ||||
|  * @raise_irq: ops to raise a legacy, MSI or MSI-X interrupt | ||||
|  * @start: ops to start the PCI link | ||||
|  * @stop: ops to stop the PCI link | ||||
|  * @owner: the module owner containing the ops | ||||
|  | @ -48,8 +53,10 @@ struct pci_epc_ops { | |||
| 			      phys_addr_t addr); | ||||
| 	int	(*set_msi)(struct pci_epc *epc, u8 func_no, u8 interrupts); | ||||
| 	int	(*get_msi)(struct pci_epc *epc, u8 func_no); | ||||
| 	int	(*set_msix)(struct pci_epc *epc, u8 func_no, u16 interrupts); | ||||
| 	int	(*get_msix)(struct pci_epc *epc, u8 func_no); | ||||
| 	int	(*raise_irq)(struct pci_epc *epc, u8 func_no, | ||||
| 			     enum pci_epc_irq_type type, u8 interrupt_num); | ||||
| 			     enum pci_epc_irq_type type, u16 interrupt_num); | ||||
| 	int	(*start)(struct pci_epc *epc); | ||||
| 	void	(*stop)(struct pci_epc *epc); | ||||
| 	struct module *owner; | ||||
|  | @ -95,6 +102,7 @@ struct pci_epc { | |||
| 
 | ||||
| #define EPC_FEATURE_NO_LINKUP_NOTIFIER		BIT(0) | ||||
| #define EPC_FEATURE_BAR_MASK			(BIT(1) | BIT(2) | BIT(3)) | ||||
| #define EPC_FEATURE_MSIX_AVAILABLE		BIT(4) | ||||
| #define EPC_FEATURE_SET_BAR(features, bar)	\ | ||||
| 		(features |= (EPC_FEATURE_BAR_MASK & (bar << 1))) | ||||
| #define EPC_FEATURE_GET_BAR(features)		\ | ||||
|  | @ -144,8 +152,10 @@ void pci_epc_unmap_addr(struct pci_epc *epc, u8 func_no, | |||
| 			phys_addr_t phys_addr); | ||||
| int pci_epc_set_msi(struct pci_epc *epc, u8 func_no, u8 interrupts); | ||||
| int pci_epc_get_msi(struct pci_epc *epc, u8 func_no); | ||||
| int pci_epc_set_msix(struct pci_epc *epc, u8 func_no, u16 interrupts); | ||||
| int pci_epc_get_msix(struct pci_epc *epc, u8 func_no); | ||||
| int pci_epc_raise_irq(struct pci_epc *epc, u8 func_no, | ||||
| 		      enum pci_epc_irq_type type, u8 interrupt_num); | ||||
| 		      enum pci_epc_irq_type type, u16 interrupt_num); | ||||
| int pci_epc_start(struct pci_epc *epc); | ||||
| void pci_epc_stop(struct pci_epc *epc); | ||||
| struct pci_epc *pci_epc_get(const char *epc_name); | ||||
|  |  | |||
|  | @ -119,6 +119,7 @@ struct pci_epf { | |||
| 	struct pci_epf_header	*header; | ||||
| 	struct pci_epf_bar	bar[6]; | ||||
| 	u8			msi_interrupts; | ||||
| 	u16			msix_interrupts; | ||||
| 	u8			func_no; | ||||
| 
 | ||||
| 	struct pci_epc		*epc; | ||||
|  |  | |||
|  | @ -16,5 +16,8 @@ | |||
| #define PCITEST_WRITE		_IOW('P', 0x4, unsigned long) | ||||
| #define PCITEST_READ		_IOW('P', 0x5, unsigned long) | ||||
| #define PCITEST_COPY		_IOW('P', 0x6, unsigned long) | ||||
| #define PCITEST_MSIX		_IOW('P', 0x7, int) | ||||
| #define PCITEST_SET_IRQTYPE	_IOW('P', 0x8, int) | ||||
| #define PCITEST_GET_IRQTYPE	_IO('P', 0x9) | ||||
| 
 | ||||
| #endif /* __UAPI_LINUX_PCITEST_H */ | ||||
|  |  | |||
|  | @ -31,12 +31,17 @@ | |||
| #define BILLION 1E9 | ||||
| 
 | ||||
| static char *result[] = { "NOT OKAY", "OKAY" }; | ||||
| static char *irq[] = { "LEGACY", "MSI", "MSI-X" }; | ||||
| 
 | ||||
| struct pci_test { | ||||
| 	char		*device; | ||||
| 	char		barnum; | ||||
| 	bool		legacyirq; | ||||
| 	unsigned int	msinum; | ||||
| 	unsigned int	msixnum; | ||||
| 	int		irqtype; | ||||
| 	bool		set_irqtype; | ||||
| 	bool		get_irqtype; | ||||
| 	bool		read; | ||||
| 	bool		write; | ||||
| 	bool		copy; | ||||
|  | @ -65,6 +70,24 @@ static int run_test(struct pci_test *test) | |||
| 			fprintf(stdout, "%s\n", result[ret]); | ||||
| 	} | ||||
| 
 | ||||
| 	if (test->set_irqtype) { | ||||
| 		ret = ioctl(fd, PCITEST_SET_IRQTYPE, test->irqtype); | ||||
| 		fprintf(stdout, "SET IRQ TYPE TO %s:\t\t", irq[test->irqtype]); | ||||
| 		if (ret < 0) | ||||
| 			fprintf(stdout, "FAILED\n"); | ||||
| 		else | ||||
| 			fprintf(stdout, "%s\n", result[ret]); | ||||
| 	} | ||||
| 
 | ||||
| 	if (test->get_irqtype) { | ||||
| 		ret = ioctl(fd, PCITEST_GET_IRQTYPE); | ||||
| 		fprintf(stdout, "GET IRQ TYPE:\t\t"); | ||||
| 		if (ret < 0) | ||||
| 			fprintf(stdout, "FAILED\n"); | ||||
| 		else | ||||
| 			fprintf(stdout, "%s\n", irq[ret]); | ||||
| 	} | ||||
| 
 | ||||
| 	if (test->legacyirq) { | ||||
| 		ret = ioctl(fd, PCITEST_LEGACY_IRQ, 0); | ||||
| 		fprintf(stdout, "LEGACY IRQ:\t"); | ||||
|  | @ -83,6 +106,15 @@ static int run_test(struct pci_test *test) | |||
| 			fprintf(stdout, "%s\n", result[ret]); | ||||
| 	} | ||||
| 
 | ||||
| 	if (test->msixnum > 0 && test->msixnum <= 2048) { | ||||
| 		ret = ioctl(fd, PCITEST_MSIX, test->msixnum); | ||||
| 		fprintf(stdout, "MSI-X%d:\t\t", test->msixnum); | ||||
| 		if (ret < 0) | ||||
| 			fprintf(stdout, "TEST FAILED\n"); | ||||
| 		else | ||||
| 			fprintf(stdout, "%s\n", result[ret]); | ||||
| 	} | ||||
| 
 | ||||
| 	if (test->write) { | ||||
| 		ret = ioctl(fd, PCITEST_WRITE, test->size); | ||||
| 		fprintf(stdout, "WRITE (%7ld bytes):\t\t", test->size); | ||||
|  | @ -133,7 +165,7 @@ int main(int argc, char **argv) | |||
| 	/* set default endpoint device */ | ||||
| 	test->device = "/dev/pci-endpoint-test.0"; | ||||
| 
 | ||||
| 	while ((c = getopt(argc, argv, "D:b:m:lrwcs:")) != EOF) | ||||
| 	while ((c = getopt(argc, argv, "D:b:m:x:i:Ilrwcs:")) != EOF) | ||||
| 	switch (c) { | ||||
| 	case 'D': | ||||
| 		test->device = optarg; | ||||
|  | @ -151,6 +183,20 @@ int main(int argc, char **argv) | |||
| 		if (test->msinum < 1 || test->msinum > 32) | ||||
| 			goto usage; | ||||
| 		continue; | ||||
| 	case 'x': | ||||
| 		test->msixnum = atoi(optarg); | ||||
| 		if (test->msixnum < 1 || test->msixnum > 2048) | ||||
| 			goto usage; | ||||
| 		continue; | ||||
| 	case 'i': | ||||
| 		test->irqtype = atoi(optarg); | ||||
| 		if (test->irqtype < 0 || test->irqtype > 2) | ||||
| 			goto usage; | ||||
| 		test->set_irqtype = true; | ||||
| 		continue; | ||||
| 	case 'I': | ||||
| 		test->get_irqtype = true; | ||||
| 		continue; | ||||
| 	case 'r': | ||||
| 		test->read = true; | ||||
| 		continue; | ||||
|  | @ -173,6 +219,9 @@ usage: | |||
| 			"\t-D <dev>		PCI endpoint test device {default: /dev/pci-endpoint-test.0}\n" | ||||
| 			"\t-b <bar num>		BAR test (bar number between 0..5)\n" | ||||
| 			"\t-m <msi num>		MSI test (msi number between 1..32)\n" | ||||
| 			"\t-x <msix num>	\tMSI-X test (msix number between 1..2048)\n" | ||||
| 			"\t-i <irq type>	\tSet IRQ type (0 - Legacy, 1 - MSI, 2 - MSI-X)\n" | ||||
| 			"\t-I			Get current IRQ type configured\n" | ||||
| 			"\t-l			Legacy IRQ test\n" | ||||
| 			"\t-r			Read buffer test\n" | ||||
| 			"\t-w			Write buffer test\n" | ||||
|  |  | |||
|  | @ -16,7 +16,10 @@ echo | |||
| echo "Interrupt tests" | ||||
| echo | ||||
| 
 | ||||
| pcitest -i 0 | ||||
| pcitest -l | ||||
| 
 | ||||
| pcitest -i 1 | ||||
| msi=1 | ||||
| 
 | ||||
| while [ $msi -lt 33 ] | ||||
|  | @ -26,9 +29,21 @@ do | |||
| done | ||||
| echo | ||||
| 
 | ||||
| pcitest -i 2 | ||||
| msix=1 | ||||
| 
 | ||||
| while [ $msix -lt 2049 ] | ||||
| do | ||||
|         pcitest -x $msix | ||||
|         msix=`expr $msix + 1` | ||||
| done | ||||
| echo | ||||
| 
 | ||||
| echo "Read Tests" | ||||
| echo | ||||
| 
 | ||||
| pcitest -i 1 | ||||
| 
 | ||||
| pcitest -r -s 1 | ||||
| pcitest -r -s 1024 | ||||
| pcitest -r -s 1025 | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Bjorn Helgaas
						Bjorn Helgaas