This patch series extends pci-bridge-emul.c driver to emulate PCI Subsystem
Vendor ID capability and PCIe extended capabilities. And then implement
in pci-mvebu.c driver support for PCI Subsystem Vendor IDs, PCIe AER
registers, support for legacy INTx interrupts, configuration for X1/X4
mode and usage of new PCI child_ops API.
This patch series depends on other pci-mvebu and pci-bridge-emul patches from:
https://lore.kernel.org/linux-pci/[email protected]/
Pali Rohár (9):
PCI: pci-bridge-emul: Add support for PCI Bridge Subsystem Vendor ID
capability
dt-bindings: PCI: mvebu: Add num-lanes property
PCI: mvebu: Correctly configure x1/x4 mode
PCI: mvebu: Add support for PCI Bridge Subsystem Vendor ID on emulated
bridge
PCI: mvebu: Add support for Advanced Error Reporting registers on
emulated bridge
PCI: mvebu: Use child_ops API
dt-bindings: PCI: mvebu: Update information about intx interrupts
PCI: mvebu: Implement support for legacy INTx interrupts
ARM: dts: armada-385.dtsi: Add definitions for PCIe legacy INTx
interrupts
Russell King (2):
PCI: pci-bridge-emul: Re-arrange register tests
PCI: pci-bridge-emul: Add support for PCIe extended capabilities
.../devicetree/bindings/pci/mvebu-pci.txt | 16 +
arch/arm/boot/dts/armada-385.dtsi | 52 ++-
drivers/pci/controller/pci-mvebu.c | 352 +++++++++++++++---
drivers/pci/pci-bridge-emul.c | 167 ++++++---
drivers/pci/pci-bridge-emul.h | 17 +
5 files changed, 494 insertions(+), 110 deletions(-)
--
2.20.1
From: Russell King <[email protected]>
Add support for PCIe extended capabilities, which we just redirect to the
emulating driver.
Signed-off-by: Russell King <[email protected]>
[pali: Fix writing new value with W1C bits]
Signed-off-by: Pali Rohár <[email protected]>
---
drivers/pci/pci-bridge-emul.c | 77 +++++++++++++++++++++++------------
drivers/pci/pci-bridge-emul.h | 15 +++++++
2 files changed, 67 insertions(+), 25 deletions(-)
diff --git a/drivers/pci/pci-bridge-emul.c b/drivers/pci/pci-bridge-emul.c
index 56b2cb741498..9f4f173f0650 100644
--- a/drivers/pci/pci-bridge-emul.c
+++ b/drivers/pci/pci-bridge-emul.c
@@ -433,10 +433,16 @@ int pci_bridge_emul_conf_read(struct pci_bridge_emul *bridge, int where,
read_op = bridge->ops->read_pcie;
cfgspace = (__le32 *) &bridge->pcie_conf;
behavior = bridge->pcie_cap_regs_behavior;
- } else {
- /* Beyond our PCIe space */
+ } else if (reg < PCI_CFG_SPACE_SIZE) {
+ /* Rest of PCI space not implemented */
*value = 0;
return PCIBIOS_SUCCESSFUL;
+ } else {
+ /* PCIe extended capability space */
+ reg -= PCI_CFG_SPACE_SIZE;
+ read_op = bridge->ops->read_ext;
+ cfgspace = NULL;
+ behavior = NULL;
}
if (read_op)
@@ -444,15 +450,20 @@ int pci_bridge_emul_conf_read(struct pci_bridge_emul *bridge, int where,
else
ret = PCI_BRIDGE_EMUL_NOT_HANDLED;
- if (ret == PCI_BRIDGE_EMUL_NOT_HANDLED)
- *value = le32_to_cpu(cfgspace[reg / 4]);
+ if (ret == PCI_BRIDGE_EMUL_NOT_HANDLED) {
+ if (cfgspace)
+ *value = le32_to_cpu(cfgspace[reg / 4]);
+ else
+ *value = 0;
+ }
/*
* Make sure we never return any reserved bit with a value
* different from 0.
*/
- *value &= behavior[reg / 4].ro | behavior[reg / 4].rw |
- behavior[reg / 4].w1c;
+ if (behavior)
+ *value &= behavior[reg / 4].ro | behavior[reg / 4].rw |
+ behavior[reg / 4].w1c;
if (size == 1)
*value = (*value >> (8 * (where & 3))) & 0xff;
@@ -498,8 +509,15 @@ int pci_bridge_emul_conf_write(struct pci_bridge_emul *bridge, int where,
write_op = bridge->ops->write_pcie;
cfgspace = (__le32 *) &bridge->pcie_conf;
behavior = bridge->pcie_cap_regs_behavior;
- } else {
+ } else if (reg < PCI_CFG_SPACE_SIZE) {
+ /* Rest of PCI space not implemented */
return PCIBIOS_SUCCESSFUL;
+ } else {
+ /* PCIe extended capability space */
+ reg -= PCI_CFG_SPACE_SIZE;
+ write_op = bridge->ops->write_ext;
+ cfgspace = NULL;
+ behavior = NULL;
}
shift = (where & 0x3) * 8;
@@ -513,29 +531,38 @@ int pci_bridge_emul_conf_write(struct pci_bridge_emul *bridge, int where,
else
return PCIBIOS_BAD_REGISTER_NUMBER;
- /* Keep all bits, except the RW bits */
- new = old & (~mask | ~behavior[reg / 4].rw);
+ if (behavior) {
+ /* Keep all bits, except the RW bits */
+ new = old & (~mask | ~behavior[reg / 4].rw);
- /* Update the value of the RW bits */
- new |= (value << shift) & (behavior[reg / 4].rw & mask);
+ /* Update the value of the RW bits */
+ new |= (value << shift) & (behavior[reg / 4].rw & mask);
- /* Clear the W1C bits */
- new &= ~((value << shift) & (behavior[reg / 4].w1c & mask));
+ /* Clear the W1C bits */
+ new &= ~((value << shift) & (behavior[reg / 4].w1c & mask));
+ } else {
+ new = old & ~mask;
+ new |= (value << shift) & mask;
+ }
- /* Save the new value with the cleared W1C bits into the cfgspace */
- cfgspace[reg / 4] = cpu_to_le32(new);
+ if (cfgspace) {
+ /* Save the new value with the cleared W1C bits into the cfgspace */
+ cfgspace[reg / 4] = cpu_to_le32(new);
+ }
- /*
- * Clear the W1C bits not specified by the write mask, so that the
- * write_op() does not clear them.
- */
- new &= ~(behavior[reg / 4].w1c & ~mask);
+ if (behavior) {
+ /*
+ * Clear the W1C bits not specified by the write mask, so that the
+ * write_op() does not clear them.
+ */
+ new &= ~(behavior[reg / 4].w1c & ~mask);
- /*
- * Set the W1C bits specified by the write mask, so that write_op()
- * knows about that they are to be cleared.
- */
- new |= (value << shift) & (behavior[reg / 4].w1c & mask);
+ /*
+ * Set the W1C bits specified by the write mask, so that write_op()
+ * knows about that they are to be cleared.
+ */
+ new |= (value << shift) & (behavior[reg / 4].w1c & mask);
+ }
if (write_op)
write_op(bridge, reg, old, new, mask);
diff --git a/drivers/pci/pci-bridge-emul.h b/drivers/pci/pci-bridge-emul.h
index 4953274cac18..6b5f75b2ad02 100644
--- a/drivers/pci/pci-bridge-emul.h
+++ b/drivers/pci/pci-bridge-emul.h
@@ -90,6 +90,14 @@ struct pci_bridge_emul_ops {
*/
pci_bridge_emul_read_status_t (*read_pcie)(struct pci_bridge_emul *bridge,
int reg, u32 *value);
+
+ /*
+ * Same as ->read_base(), except it is for reading from the
+ * PCIe extended capability configuration space.
+ */
+ pci_bridge_emul_read_status_t (*read_ext)(struct pci_bridge_emul *bridge,
+ int reg, u32 *value);
+
/*
* Called when writing to the regular PCI bridge configuration
* space. old is the current value, new is the new value being
@@ -105,6 +113,13 @@ struct pci_bridge_emul_ops {
*/
void (*write_pcie)(struct pci_bridge_emul *bridge, int reg,
u32 old, u32 new, u32 mask);
+
+ /*
+ * Same as ->write_base(), except it is for writing from the
+ * PCIe extended capability configuration space.
+ */
+ void (*write_ext)(struct pci_bridge_emul *bridge, int reg,
+ u32 old, u32 new, u32 mask);
};
struct pci_bridge_reg_behavior;
--
2.20.1
This is read-only capability in PCI config space. Put it between base PCI
capability and base PCI Express capability.
Driver just have to specify subsystem_vendor_id and subsystem_id fields in
emulated bridge structure and pci-bridge-emul takes care of correctly
compose PCI Bridge Subsystem Vendor ID capability.
Signed-off-by: Pali Rohár <[email protected]>
---
drivers/pci/pci-bridge-emul.c | 69 +++++++++++++++++++++++++----------
drivers/pci/pci-bridge-emul.h | 2 +
2 files changed, 51 insertions(+), 20 deletions(-)
diff --git a/drivers/pci/pci-bridge-emul.c b/drivers/pci/pci-bridge-emul.c
index 9f4f173f0650..c84f423a5893 100644
--- a/drivers/pci/pci-bridge-emul.c
+++ b/drivers/pci/pci-bridge-emul.c
@@ -21,8 +21,11 @@
#include "pci-bridge-emul.h"
#define PCI_BRIDGE_CONF_END PCI_STD_HEADER_SIZEOF
+#define PCI_CAP_SSID_SIZEOF (PCI_SSVID_DEVICE_ID + 2)
+#define PCI_CAP_SSID_START PCI_BRIDGE_CONF_END
+#define PCI_CAP_SSID_END (PCI_CAP_SSID_START + PCI_CAP_SSID_SIZEOF)
#define PCI_CAP_PCIE_SIZEOF (PCI_EXP_SLTSTA2 + 2)
-#define PCI_CAP_PCIE_START PCI_BRIDGE_CONF_END
+#define PCI_CAP_PCIE_START PCI_CAP_SSID_END
#define PCI_CAP_PCIE_END (PCI_CAP_PCIE_START + PCI_CAP_PCIE_SIZEOF)
/**
@@ -315,6 +318,25 @@ struct pci_bridge_reg_behavior pcie_cap_regs_behavior[PCI_CAP_PCIE_SIZEOF / 4] =
},
};
+static pci_bridge_emul_read_status_t
+pci_bridge_emul_read_ssid(struct pci_bridge_emul *bridge, int reg, u32 *value)
+{
+ switch (reg) {
+ case PCI_CAP_LIST_ID:
+ *value = PCI_CAP_ID_SSVID |
+ (bridge->has_pcie ? (PCI_CAP_PCIE_START << 8) : 0);
+ return PCI_BRIDGE_EMUL_HANDLED;
+
+ case PCI_SSVID_VENDOR_ID:
+ *value = bridge->subsystem_vendor_id |
+ (bridge->subsystem_id << 16);
+ return PCI_BRIDGE_EMUL_HANDLED;
+
+ default:
+ return PCI_BRIDGE_EMUL_NOT_HANDLED;
+ }
+}
+
/*
* Initialize a pci_bridge_emul structure to represent a fake PCI
* bridge configuration space. The caller needs to have initialized
@@ -337,9 +359,17 @@ int pci_bridge_emul_init(struct pci_bridge_emul *bridge,
if (!bridge->pci_regs_behavior)
return -ENOMEM;
- if (bridge->has_pcie) {
+ if (bridge->subsystem_vendor_id)
+ bridge->conf.capabilities_pointer = PCI_CAP_SSID_START;
+ else if (bridge->has_pcie)
bridge->conf.capabilities_pointer = PCI_CAP_PCIE_START;
+ else
+ bridge->conf.capabilities_pointer = 0;
+
+ if (bridge->conf.capabilities_pointer)
bridge->conf.status |= cpu_to_le16(PCI_STATUS_CAP_LIST);
+
+ if (bridge->has_pcie) {
bridge->pcie_conf.cap_id = PCI_CAP_ID_EXP;
bridge->pcie_conf.cap |= cpu_to_le16(PCI_EXP_TYPE_ROOT_PORT << 4);
bridge->pcie_cap_regs_behavior =
@@ -423,26 +453,28 @@ int pci_bridge_emul_conf_read(struct pci_bridge_emul *bridge, int where,
read_op = bridge->ops->read_base;
cfgspace = (__le32 *) &bridge->conf;
behavior = bridge->pci_regs_behavior;
- } else if (!bridge->has_pcie) {
- /* PCIe space is not implemented, and no PCI capabilities */
- *value = 0;
- return PCIBIOS_SUCCESSFUL;
- } else if (reg < PCI_CAP_PCIE_END) {
+ } else if (reg >= PCI_CAP_SSID_START && reg < PCI_CAP_SSID_END && bridge->subsystem_vendor_id) {
+ /* Emulated PCI Bridge Subsystem Vendor ID capability */
+ reg -= PCI_CAP_SSID_START;
+ read_op = pci_bridge_emul_read_ssid;
+ cfgspace = NULL;
+ behavior = NULL;
+ } else if (reg >= PCI_CAP_PCIE_START && reg < PCI_CAP_PCIE_END && bridge->has_pcie) {
/* Our emulated PCIe capability */
reg -= PCI_CAP_PCIE_START;
read_op = bridge->ops->read_pcie;
cfgspace = (__le32 *) &bridge->pcie_conf;
behavior = bridge->pcie_cap_regs_behavior;
- } else if (reg < PCI_CFG_SPACE_SIZE) {
- /* Rest of PCI space not implemented */
- *value = 0;
- return PCIBIOS_SUCCESSFUL;
- } else {
+ } else if (reg >= PCI_CFG_SPACE_SIZE && bridge->has_pcie) {
/* PCIe extended capability space */
reg -= PCI_CFG_SPACE_SIZE;
read_op = bridge->ops->read_ext;
cfgspace = NULL;
behavior = NULL;
+ } else {
+ /* Not implemented */
+ *value = 0;
+ return PCIBIOS_SUCCESSFUL;
}
if (read_op)
@@ -500,24 +532,21 @@ int pci_bridge_emul_conf_write(struct pci_bridge_emul *bridge, int where,
write_op = bridge->ops->write_base;
cfgspace = (__le32 *) &bridge->conf;
behavior = bridge->pci_regs_behavior;
- } else if (!bridge->has_pcie) {
- /* PCIe space is not implemented, and no PCI capabilities */
- return PCIBIOS_SUCCESSFUL;
- } else if (reg < PCI_CAP_PCIE_END) {
+ } else if (reg >= PCI_CAP_PCIE_START && reg < PCI_CAP_PCIE_END && bridge->has_pcie) {
/* Our emulated PCIe capability */
reg -= PCI_CAP_PCIE_START;
write_op = bridge->ops->write_pcie;
cfgspace = (__le32 *) &bridge->pcie_conf;
behavior = bridge->pcie_cap_regs_behavior;
- } else if (reg < PCI_CFG_SPACE_SIZE) {
- /* Rest of PCI space not implemented */
- return PCIBIOS_SUCCESSFUL;
- } else {
+ } else if (reg >= PCI_CFG_SPACE_SIZE && bridge->has_pcie) {
/* PCIe extended capability space */
reg -= PCI_CFG_SPACE_SIZE;
write_op = bridge->ops->write_ext;
cfgspace = NULL;
behavior = NULL;
+ } else {
+ /* Not implemented */
+ return PCIBIOS_SUCCESSFUL;
}
shift = (where & 0x3) * 8;
diff --git a/drivers/pci/pci-bridge-emul.h b/drivers/pci/pci-bridge-emul.h
index 6b5f75b2ad02..71392b67471d 100644
--- a/drivers/pci/pci-bridge-emul.h
+++ b/drivers/pci/pci-bridge-emul.h
@@ -132,6 +132,8 @@ struct pci_bridge_emul {
struct pci_bridge_reg_behavior *pcie_cap_regs_behavior;
void *data;
bool has_pcie;
+ u16 subsystem_vendor_id;
+ u16 subsystem_id;
};
enum {
--
2.20.1
AER registers start at mvebu offset 0x0100. Registers PCI_ERR_ROOT_COMMAND,
PCI_ERR_ROOT_STATUS and PCI_ERR_ROOT_ERR_SRC are not supported on pre-XP
hardware and returns zeros.
Note that AER interrupt is not supported yet as mvebu emulated bridge does
not implement interrupts support at all yet.
Also remove custom macro PCIE_HEADER_LOG_4_OFF as it is unused and
correctly this register should be referenced via standard macros with
offset, e.g. as: PCIE_CAP_PCIERR_OFF + PCI_ERR_HEADER_LOG + 4.
Signed-off-by: Pali Rohár <[email protected]>
---
drivers/pci/controller/pci-mvebu.c | 67 +++++++++++++++++++++++++++++-
1 file changed, 66 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
index 811af9e6ede5..9ea2f6a7c2b0 100644
--- a/drivers/pci/controller/pci-mvebu.c
+++ b/drivers/pci/controller/pci-mvebu.c
@@ -34,7 +34,7 @@
#define PCIE_BAR_HI_OFF(n) (0x0014 + ((n) << 3))
#define PCIE_SSDEV_ID_OFF 0x002c
#define PCIE_CAP_PCIEXP 0x0060
-#define PCIE_HEADER_LOG_4_OFF 0x0128
+#define PCIE_CAP_PCIERR_OFF 0x0100
#define PCIE_BAR_CTRL_OFF(n) (0x1804 + (((n) - 1) * 4))
#define PCIE_WIN04_CTRL_OFF(n) (0x1820 + ((n) << 4))
#define PCIE_WIN04_BASE_OFF(n) (0x1824 + ((n) << 4))
@@ -603,6 +603,37 @@ mvebu_pci_bridge_emul_pcie_conf_read(struct pci_bridge_emul *bridge,
return PCI_BRIDGE_EMUL_HANDLED;
}
+static pci_bridge_emul_read_status_t
+mvebu_pci_bridge_emul_ext_conf_read(struct pci_bridge_emul *bridge,
+ int reg, u32 *value)
+{
+ struct mvebu_pcie_port *port = bridge->data;
+
+ switch (reg) {
+ case 0:
+ case PCI_ERR_UNCOR_STATUS:
+ case PCI_ERR_UNCOR_MASK:
+ case PCI_ERR_UNCOR_SEVER:
+ case PCI_ERR_COR_STATUS:
+ case PCI_ERR_COR_MASK:
+ case PCI_ERR_CAP:
+ case PCI_ERR_HEADER_LOG+0:
+ case PCI_ERR_HEADER_LOG+4:
+ case PCI_ERR_HEADER_LOG+8:
+ case PCI_ERR_HEADER_LOG+12:
+ case PCI_ERR_ROOT_COMMAND:
+ case PCI_ERR_ROOT_STATUS:
+ case PCI_ERR_ROOT_ERR_SRC:
+ *value = mvebu_readl(port, PCIE_CAP_PCIERR_OFF + reg);
+ break;
+
+ default:
+ return PCI_BRIDGE_EMUL_NOT_HANDLED;
+ }
+
+ return PCI_BRIDGE_EMUL_HANDLED;
+}
+
static void
mvebu_pci_bridge_emul_base_conf_write(struct pci_bridge_emul *bridge,
int reg, u32 old, u32 new, u32 mask)
@@ -715,11 +746,45 @@ mvebu_pci_bridge_emul_pcie_conf_write(struct pci_bridge_emul *bridge,
}
}
+static void
+mvebu_pci_bridge_emul_ext_conf_write(struct pci_bridge_emul *bridge,
+ int reg, u32 old, u32 new, u32 mask)
+{
+ struct mvebu_pcie_port *port = bridge->data;
+
+ switch (reg) {
+ /* These are W1C registers, so clear other bits */
+ case PCI_ERR_UNCOR_STATUS:
+ case PCI_ERR_COR_STATUS:
+ case PCI_ERR_ROOT_STATUS:
+ new &= mask;
+ fallthrough;
+
+ case PCI_ERR_UNCOR_MASK:
+ case PCI_ERR_UNCOR_SEVER:
+ case PCI_ERR_COR_MASK:
+ case PCI_ERR_CAP:
+ case PCI_ERR_HEADER_LOG+0:
+ case PCI_ERR_HEADER_LOG+4:
+ case PCI_ERR_HEADER_LOG+8:
+ case PCI_ERR_HEADER_LOG+12:
+ case PCI_ERR_ROOT_COMMAND:
+ case PCI_ERR_ROOT_ERR_SRC:
+ mvebu_writel(port, new, PCIE_CAP_PCIERR_OFF + reg);
+ break;
+
+ default:
+ break;
+ }
+}
+
static const struct pci_bridge_emul_ops mvebu_pci_bridge_emul_ops = {
.read_base = mvebu_pci_bridge_emul_base_conf_read,
.write_base = mvebu_pci_bridge_emul_base_conf_write,
.read_pcie = mvebu_pci_bridge_emul_pcie_conf_read,
.write_pcie = mvebu_pci_bridge_emul_pcie_conf_write,
+ .read_ext = mvebu_pci_bridge_emul_ext_conf_read,
+ .write_ext = mvebu_pci_bridge_emul_ext_conf_write,
};
/*
--
2.20.1
Register with Subsystem Device/Vendor ID is at offset 0x2c. Export is via
emulated bridge.
After this change Subsystem ID is visible in lspci output at line:
Capabilities: [40] Subsystem
Signed-off-by: Pali Rohár <[email protected]>
---
drivers/pci/controller/pci-mvebu.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
index 0f2ec0a17874..811af9e6ede5 100644
--- a/drivers/pci/controller/pci-mvebu.c
+++ b/drivers/pci/controller/pci-mvebu.c
@@ -32,6 +32,7 @@
#define PCIE_DEV_REV_OFF 0x0008
#define PCIE_BAR_LO_OFF(n) (0x0010 + ((n) << 3))
#define PCIE_BAR_HI_OFF(n) (0x0014 + ((n) << 3))
+#define PCIE_SSDEV_ID_OFF 0x002c
#define PCIE_CAP_PCIEXP 0x0060
#define PCIE_HEADER_LOG_4_OFF 0x0128
#define PCIE_BAR_CTRL_OFF(n) (0x1804 + (((n) - 1) * 4))
@@ -731,6 +732,7 @@ static int mvebu_pci_bridge_emul_init(struct mvebu_pcie_port *port)
struct pci_bridge_emul *bridge = &port->bridge;
u32 dev_id = mvebu_readl(port, PCIE_DEV_ID_OFF);
u32 dev_rev = mvebu_readl(port, PCIE_DEV_REV_OFF);
+ u32 ssdev_id = mvebu_readl(port, PCIE_SSDEV_ID_OFF);
u32 pcie_cap = mvebu_readl(port, PCIE_CAP_PCIEXP);
u8 pcie_cap_ver = ((pcie_cap >> 16) & PCI_EXP_FLAGS_VERS);
@@ -752,6 +754,8 @@ static int mvebu_pci_bridge_emul_init(struct mvebu_pcie_port *port)
*/
bridge->pcie_conf.cap = cpu_to_le16(pcie_cap_ver);
+ bridge->subsystem_vendor_id = ssdev_id & 0xffff;
+ bridge->subsystem_id = ssdev_id >> 16;
bridge->has_pcie = true;
bridge->data = port;
bridge->ops = &mvebu_pci_bridge_emul_ops;
--
2.20.1
If x1/x4 mode is not set correctly then link with endpoint card is not
established.
Use DTS property 'num-lanes' to deteriminate x1/x4 mode.
Signed-off-by: Pali Rohár <[email protected]>
---
drivers/pci/controller/pci-mvebu.c | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
index a075ba26cff1..0f2ec0a17874 100644
--- a/drivers/pci/controller/pci-mvebu.c
+++ b/drivers/pci/controller/pci-mvebu.c
@@ -93,6 +93,7 @@ struct mvebu_pcie_port {
void __iomem *base;
u32 port;
u32 lane;
+ bool is_x4;
int devfn;
unsigned int mem_target;
unsigned int mem_attr;
@@ -233,13 +234,25 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
{
- u32 ctrl, cmd, dev_rev, mask;
+ u32 ctrl, lnkcap, cmd, dev_rev, mask;
/* Setup PCIe controller to Root Complex mode. */
ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
ctrl |= PCIE_CTRL_RC_MODE;
mvebu_writel(port, ctrl, PCIE_CTRL_OFF);
+ /*
+ * Set Maximum Link Width to X1 or X4 in Root Port's PCIe Link
+ * Capability register. This register is defined by PCIe specification
+ * as read-only but this mvebu controller has it as read-write and must
+ * be set to number of SerDes PCIe lanes (1 or 4). If this register is
+ * not set correctly then link with endpoint card is not established.
+ */
+ lnkcap = mvebu_readl(port, PCIE_CAP_PCIEXP + PCI_EXP_LNKCAP);
+ lnkcap &= ~PCI_EXP_LNKCAP_MLW;
+ lnkcap |= (port->is_x4 ? 4 : 1) << 4;
+ mvebu_writel(port, lnkcap, PCIE_CAP_PCIEXP + PCI_EXP_LNKCAP);
+
/* Disable Root Bridge I/O space, memory space and bus mastering. */
cmd = mvebu_readl(port, PCIE_CMD_OFF);
cmd &= ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
@@ -986,6 +999,7 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
struct device *dev = &pcie->pdev->dev;
enum of_gpio_flags flags;
int reset_gpio, ret;
+ u32 num_lanes;
port->pcie = pcie;
@@ -998,6 +1012,9 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
if (of_property_read_u32(child, "marvell,pcie-lane", &port->lane))
port->lane = 0;
+ if (!of_property_read_u32(child, "num-lanes", &num_lanes) && num_lanes == 4)
+ port->is_x4 = true;
+
port->name = devm_kasprintf(dev, GFP_KERNEL, "pcie%d.%d", port->port,
port->lane);
if (!port->name) {
--
2.20.1
Signed-off-by: Pali Rohár <[email protected]>
---
Documentation/devicetree/bindings/pci/mvebu-pci.txt | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Documentation/devicetree/bindings/pci/mvebu-pci.txt b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
index 24225852bce0..6d022a9d36ee 100644
--- a/Documentation/devicetree/bindings/pci/mvebu-pci.txt
+++ b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
@@ -81,6 +81,11 @@ and the following optional properties:
- reset-gpios: optional GPIO to PERST#
- reset-delay-us: delay in us to wait after reset de-assertion, if not
specified will default to 100ms, as required by the PCIe specification.
+- interrupt-names: list of interrupt names, supported are:
+ - "intx" - interrupt line triggered by one of the legacy interrupt
+- interrupts or interrupts-extended: List of the interrupt sources which
+ corresponding to the "interrupt-names". If non-empty then also additional
+ 'interrupt-controller' subnode must be defined.
Example:
--
2.20.1
Split struct pci_ops between ops and child_ops. Member ops is used for
accessing PCIe Root Ports via pci-bridge-emul.c driver and child_ops for
accessing real PCIe cards.
There is no need to mix these two struct pci_ops into one as PCI core code
already provides separate callbacks via bridge->ops and bridge->child_ops.
Signed-off-by: Pali Rohár <[email protected]>
---
drivers/pci/controller/pci-mvebu.c | 82 ++++++++++++++++--------------
1 file changed, 44 insertions(+), 38 deletions(-)
diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
index 9ea2f6a7c2b0..1e90ab888075 100644
--- a/drivers/pci/controller/pci-mvebu.c
+++ b/drivers/pci/controller/pci-mvebu.c
@@ -294,11 +294,29 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
mvebu_writel(port, mask, PCIE_MASK_OFF);
}
-static int mvebu_pcie_hw_rd_conf(struct mvebu_pcie_port *port,
- struct pci_bus *bus,
- u32 devfn, int where, int size, u32 *val)
+static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
+ struct pci_bus *bus,
+ int devfn);
+
+static int mvebu_pcie_child_rd_conf(struct pci_bus *bus, u32 devfn, int where,
+ int size, u32 *val)
{
- void __iomem *conf_data = port->base + PCIE_CONF_DATA_OFF;
+ struct mvebu_pcie *pcie = bus->sysdata;
+ struct mvebu_pcie_port *port;
+ void __iomem *conf_data;
+
+ port = mvebu_pcie_find_port(pcie, bus, devfn);
+ if (!port) {
+ *val = 0xffffffff;
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ }
+
+ if (!mvebu_pcie_link_up(port)) {
+ *val = 0xffffffff;
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ }
+
+ conf_data = port->base + PCIE_CONF_DATA_OFF;
mvebu_writel(port, PCIE_CONF_ADDR(bus->number, devfn, where),
PCIE_CONF_ADDR_OFF);
@@ -321,11 +339,21 @@ static int mvebu_pcie_hw_rd_conf(struct mvebu_pcie_port *port,
return PCIBIOS_SUCCESSFUL;
}
-static int mvebu_pcie_hw_wr_conf(struct mvebu_pcie_port *port,
- struct pci_bus *bus,
- u32 devfn, int where, int size, u32 val)
+static int mvebu_pcie_child_wr_conf(struct pci_bus *bus, u32 devfn,
+ int where, int size, u32 val)
{
- void __iomem *conf_data = port->base + PCIE_CONF_DATA_OFF;
+ struct mvebu_pcie *pcie = bus->sysdata;
+ struct mvebu_pcie_port *port;
+ void __iomem *conf_data;
+
+ port = mvebu_pcie_find_port(pcie, bus, devfn);
+ if (!port)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ if (!mvebu_pcie_link_up(port))
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ conf_data = port->base + PCIE_CONF_DATA_OFF;
mvebu_writel(port, PCIE_CONF_ADDR(bus->number, devfn, where),
PCIE_CONF_ADDR_OFF);
@@ -347,6 +375,11 @@ static int mvebu_pcie_hw_wr_conf(struct mvebu_pcie_port *port,
return PCIBIOS_SUCCESSFUL;
}
+static struct pci_ops mvebu_pcie_child_ops = {
+ .read = mvebu_pcie_child_rd_conf,
+ .write = mvebu_pcie_child_wr_conf,
+};
+
/*
* Remove windows, starting from the largest ones to the smallest
* ones.
@@ -862,25 +895,12 @@ static int mvebu_pcie_wr_conf(struct pci_bus *bus, u32 devfn,
{
struct mvebu_pcie *pcie = bus->sysdata;
struct mvebu_pcie_port *port;
- int ret;
port = mvebu_pcie_find_port(pcie, bus, devfn);
if (!port)
return PCIBIOS_DEVICE_NOT_FOUND;
- /* Access the emulated PCI-to-PCI bridge */
- if (bus->number == 0)
- return pci_bridge_emul_conf_write(&port->bridge, where,
- size, val);
-
- if (!mvebu_pcie_link_up(port))
- return PCIBIOS_DEVICE_NOT_FOUND;
-
- /* Access the real PCIe interface */
- ret = mvebu_pcie_hw_wr_conf(port, bus, devfn,
- where, size, val);
-
- return ret;
+ return pci_bridge_emul_conf_write(&port->bridge, where, size, val);
}
/* PCI configuration space read function */
@@ -889,7 +909,6 @@ static int mvebu_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where,
{
struct mvebu_pcie *pcie = bus->sysdata;
struct mvebu_pcie_port *port;
- int ret;
port = mvebu_pcie_find_port(pcie, bus, devfn);
if (!port) {
@@ -897,21 +916,7 @@ static int mvebu_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where,
return PCIBIOS_DEVICE_NOT_FOUND;
}
- /* Access the emulated PCI-to-PCI bridge */
- if (bus->number == 0)
- return pci_bridge_emul_conf_read(&port->bridge, where,
- size, val);
-
- if (!mvebu_pcie_link_up(port)) {
- *val = 0xffffffff;
- return PCIBIOS_DEVICE_NOT_FOUND;
- }
-
- /* Access the real PCIe interface */
- ret = mvebu_pcie_hw_rd_conf(port, bus, devfn,
- where, size, val);
-
- return ret;
+ return pci_bridge_emul_conf_read(&port->bridge, where, size, val);
}
static struct pci_ops mvebu_pcie_ops = {
@@ -1421,6 +1426,7 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
bridge->sysdata = pcie;
bridge->ops = &mvebu_pcie_ops;
+ bridge->child_ops = &mvebu_pcie_child_ops;
bridge->align_resource = mvebu_pcie_align_resource;
bridge->map_irq = mvebu_pcie_map_irq;
--
2.20.1
This adds support for legacy INTx interrupts received from other PCIe
devices and which are reported by a new INTx irq chip.
With this change, kernel can distinguish between INTA, INTB, INTC and INTD
interrupts.
Note that for this support, device tree files has to be properly adjusted
to provide "interrupts" or "interrupts-extended" property with intx
interrupt source, "interrupt-names" property with "intx" string and also
'interrupt-controller' subnode must be defined.
If device tree files do not provide these nodes then driver would work as
before.
Signed-off-by: Pali Rohár <[email protected]>
---
drivers/pci/controller/pci-mvebu.c | 182 +++++++++++++++++++++++++++--
1 file changed, 174 insertions(+), 8 deletions(-)
diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
index 1e90ab888075..04bcdd7b7a6d 100644
--- a/drivers/pci/controller/pci-mvebu.c
+++ b/drivers/pci/controller/pci-mvebu.c
@@ -54,9 +54,10 @@
PCIE_CONF_ADDR_EN)
#define PCIE_CONF_DATA_OFF 0x18fc
#define PCIE_INT_CAUSE_OFF 0x1900
+#define PCIE_INT_UNMASK_OFF 0x1910
+#define PCIE_INT_INTX(i) BIT(24+i)
#define PCIE_INT_PM_PME BIT(28)
-#define PCIE_MASK_OFF 0x1910
-#define PCIE_MASK_ENABLE_INTS 0x0f000000
+#define PCIE_INT_ALL_MASK GENMASK(31, 0)
#define PCIE_CTRL_OFF 0x1a00
#define PCIE_CTRL_X1_MODE 0x0001
#define PCIE_CTRL_RC_MODE BIT(1)
@@ -110,6 +111,10 @@ struct mvebu_pcie_port {
struct mvebu_pcie_window iowin;
u32 saved_pcie_stat;
struct resource regs;
+ struct irq_domain *intx_irq_domain;
+ struct irq_chip intx_irq_chip;
+ raw_spinlock_t irq_lock;
+ int intx_irq;
};
static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
@@ -235,7 +240,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
{
- u32 ctrl, lnkcap, cmd, dev_rev, mask;
+ u32 ctrl, lnkcap, cmd, dev_rev, unmask;
/* Setup PCIe controller to Root Complex mode. */
ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
@@ -288,10 +293,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
/* Point PCIe unit MBUS decode windows to DRAM space. */
mvebu_pcie_setup_wins(port);
- /* Enable interrupt lines A-D. */
- mask = mvebu_readl(port, PCIE_MASK_OFF);
- mask |= PCIE_MASK_ENABLE_INTS;
- mvebu_writel(port, mask, PCIE_MASK_OFF);
+ /* Mask all interrupt sources. */
+ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
+
+ /* Clear all interrupt causes. */
+ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
+
+ if (port->intx_irq <= 0) {
+ /*
+ * When neither "summary" interrupt, nor "intx" interrupt was
+ * specified in DT then unmask all legacy INTx interrupts as in
+ * this case driver does not provide a way for masking and
+ * unmasking of individual legacy INTx interrupts. In this case
+ * all interrupts, including legacy INTx are reported via one
+ * shared GIC source and therefore kernel cannot distinguish
+ * which individual legacy INTx was triggered. These interrupts
+ * are shared, so it should not cause any issue. Just
+ * performance penalty as every PCIe interrupt handler needs to
+ * be called when some interrupt is triggered.
+ */
+ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+ unmask |= PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
+ PCIE_INT_INTX(2) | PCIE_INT_INTX(3);
+ mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
+ }
}
static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
@@ -924,6 +949,109 @@ static struct pci_ops mvebu_pcie_ops = {
.write = mvebu_pcie_wr_conf,
};
+static void mvebu_pcie_intx_irq_mask(struct irq_data *d)
+{
+ struct mvebu_pcie_port *port = d->domain->host_data;
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+ unsigned long flags;
+ u32 unmask;
+
+ raw_spin_lock_irqsave(&port->irq_lock, flags);
+ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+ unmask &= ~PCIE_INT_INTX(hwirq);
+ mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
+ raw_spin_unlock_irqrestore(&port->irq_lock, flags);
+}
+
+static void mvebu_pcie_intx_irq_unmask(struct irq_data *d)
+{
+ struct mvebu_pcie_port *port = d->domain->host_data;
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+ unsigned long flags;
+ u32 unmask;
+
+ raw_spin_lock_irqsave(&port->irq_lock, flags);
+ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+ unmask |= PCIE_INT_INTX(hwirq);
+ mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
+ raw_spin_unlock_irqrestore(&port->irq_lock, flags);
+}
+
+static int mvebu_pcie_intx_irq_map(struct irq_domain *h,
+ unsigned int virq, irq_hw_number_t hwirq)
+{
+ struct mvebu_pcie_port *port = h->host_data;
+
+ irq_set_status_flags(virq, IRQ_LEVEL);
+ irq_set_chip_and_handler(virq, &port->intx_irq_chip, handle_level_irq);
+ irq_set_chip_data(virq, port);
+
+ return 0;
+}
+
+static const struct irq_domain_ops mvebu_pcie_intx_irq_domain_ops = {
+ .map = mvebu_pcie_intx_irq_map,
+ .xlate = irq_domain_xlate_onecell,
+};
+
+static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
+{
+ struct device *dev = &port->pcie->pdev->dev;
+ struct device_node *pcie_intc_node;
+
+ raw_spin_lock_init(&port->irq_lock);
+
+ port->intx_irq_chip.name = devm_kasprintf(dev, GFP_KERNEL,
+ "mvebu-%s-INTx",
+ port->name);
+ port->intx_irq_chip.irq_mask = mvebu_pcie_intx_irq_mask;
+ port->intx_irq_chip.irq_unmask = mvebu_pcie_intx_irq_unmask;
+
+ pcie_intc_node = of_get_next_child(port->dn, NULL);
+ if (!pcie_intc_node) {
+ dev_err(dev, "No PCIe Intc node found for %s\n", port->name);
+ return -ENODEV;
+ }
+
+ port->intx_irq_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX,
+ &mvebu_pcie_intx_irq_domain_ops,
+ port);
+ of_node_put(pcie_intc_node);
+ if (!port->intx_irq_domain) {
+ devm_kfree(dev, port->intx_irq_chip.name);
+ dev_err(dev, "Failed to get INTx IRQ domain for %s\n", port->name);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void mvebu_pcie_irq_handler(struct irq_desc *desc)
+{
+ struct mvebu_pcie_port *port = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ struct device *dev = &port->pcie->pdev->dev;
+ u32 cause, unmask, status;
+ int i;
+
+ chained_irq_enter(chip, desc);
+
+ cause = mvebu_readl(port, PCIE_INT_CAUSE_OFF);
+ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+ status = cause & unmask;
+
+ /* Process legacy INTx interrupts */
+ for (i = 0; i < PCI_NUM_INTX; i++) {
+ if (!(status & PCIE_INT_INTX(i)))
+ continue;
+
+ if (generic_handle_domain_irq(port->intx_irq_domain, i) == -EINVAL)
+ dev_err_ratelimited(dev, "unexpected INT%c IRQ\n", (char)i+'A');
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
static int mvebu_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
{
/* Interrupt support on mvebu emulated bridges is not implemented yet */
@@ -1121,6 +1249,16 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
port->io_attr = -1;
}
+ /*
+ * Old DT bindings do not contain "intx" interrupt
+ * so do not fail probing driver when interrupt does not exist.
+ */
+ port->intx_irq = of_irq_get_byname(child, "intx");
+ if (port->intx_irq == -EPROBE_DEFER) {
+ ret = port->intx_irq;
+ goto err;
+ }
+
reset_gpio = of_get_named_gpio_flags(child, "reset-gpios", 0, &flags);
if (reset_gpio == -EPROBE_DEFER) {
ret = reset_gpio;
@@ -1317,6 +1455,7 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
for (i = 0; i < pcie->nports; i++) {
struct mvebu_pcie_port *port = &pcie->ports[i];
+ int irq = port->intx_irq;
child = port->dn;
if (!child)
@@ -1344,6 +1483,22 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
continue;
}
+ if (irq > 0) {
+ ret = mvebu_pcie_init_irq_domain(port);
+ if (ret) {
+ dev_err(dev, "%s: cannot init irq domain\n",
+ port->name);
+ pci_bridge_emul_cleanup(&port->bridge);
+ devm_iounmap(dev, port->base);
+ port->base = NULL;
+ mvebu_pcie_powerdown(port);
+ continue;
+ }
+ irq_set_chained_handler_and_data(irq,
+ mvebu_pcie_irq_handler,
+ port);
+ }
+
/*
* PCIe topology exported by mvebu hw is quite complicated. In
* reality has something like N fully independent host bridges
@@ -1448,6 +1603,7 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
for (i = 0; i < pcie->nports; i++) {
struct mvebu_pcie_port *port = &pcie->ports[i];
+ int irq = port->intx_irq;
if (!port->base)
continue;
@@ -1458,7 +1614,17 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
mvebu_writel(port, cmd, PCIE_CMD_OFF);
/* Mask all interrupt sources. */
- mvebu_writel(port, 0, PCIE_MASK_OFF);
+ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
+
+ /* Clear all interrupt causes. */
+ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
+
+ /* Remove IRQ domains. */
+ if (port->intx_irq_domain)
+ irq_domain_remove(port->intx_irq_domain);
+
+ if (irq > 0)
+ irq_set_chained_handler_and_data(irq, NULL, NULL);
/* Free config space for emulated root bridge. */
pci_bridge_emul_cleanup(&port->bridge);
--
2.20.1
With this change legacy INTA, INTB, INTC and INTD interrupts are reported
separately and not mixed into one Linux virq source anymore.
Signed-off-by: Pali Rohár <[email protected]>
---
arch/arm/boot/dts/armada-385.dtsi | 52 ++++++++++++++++++++++++++-----
1 file changed, 44 insertions(+), 8 deletions(-)
diff --git a/arch/arm/boot/dts/armada-385.dtsi b/arch/arm/boot/dts/armada-385.dtsi
index f0022d10c715..83392b92dae2 100644
--- a/arch/arm/boot/dts/armada-385.dtsi
+++ b/arch/arm/boot/dts/armada-385.dtsi
@@ -69,16 +69,25 @@
reg = <0x0800 0 0 0 0>;
#address-cells = <3>;
#size-cells = <2>;
+ interrupt-names = "intx";
+ interrupts-extended = <&gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
#interrupt-cells = <1>;
ranges = <0x82000000 0 0 0x82000000 0x1 0 1 0
0x81000000 0 0 0x81000000 0x1 0 1 0>;
bus-range = <0x00 0xff>;
- interrupt-map-mask = <0 0 0 0>;
- interrupt-map = <0 0 0 0 &gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-map-mask = <0 0 0 7>;
+ interrupt-map = <0 0 0 1 &pcie1_intc 0>,
+ <0 0 0 2 &pcie1_intc 1>,
+ <0 0 0 3 &pcie1_intc 2>,
+ <0 0 0 4 &pcie1_intc 3>;
marvell,pcie-port = <0>;
marvell,pcie-lane = <0>;
clocks = <&gateclk 8>;
status = "disabled";
+ pcie1_intc: interrupt-controller {
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ };
};
/* x1 port */
@@ -88,16 +97,25 @@
reg = <0x1000 0 0 0 0>;
#address-cells = <3>;
#size-cells = <2>;
+ interrupt-names = "intx";
+ interrupts-extended = <&gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
#interrupt-cells = <1>;
ranges = <0x82000000 0 0 0x82000000 0x2 0 1 0
0x81000000 0 0 0x81000000 0x2 0 1 0>;
bus-range = <0x00 0xff>;
- interrupt-map-mask = <0 0 0 0>;
- interrupt-map = <0 0 0 0 &gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-map-mask = <0 0 0 7>;
+ interrupt-map = <0 0 0 1 &pcie2_intc 0>,
+ <0 0 0 2 &pcie2_intc 1>,
+ <0 0 0 3 &pcie2_intc 2>,
+ <0 0 0 4 &pcie2_intc 3>;
marvell,pcie-port = <1>;
marvell,pcie-lane = <0>;
clocks = <&gateclk 5>;
status = "disabled";
+ pcie2_intc: interrupt-controller {
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ };
};
/* x1 port */
@@ -107,16 +125,25 @@
reg = <0x1800 0 0 0 0>;
#address-cells = <3>;
#size-cells = <2>;
+ interrupt-names = "intx";
+ interrupts-extended = <&gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
#interrupt-cells = <1>;
ranges = <0x82000000 0 0 0x82000000 0x3 0 1 0
0x81000000 0 0 0x81000000 0x3 0 1 0>;
bus-range = <0x00 0xff>;
- interrupt-map-mask = <0 0 0 0>;
- interrupt-map = <0 0 0 0 &gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-map-mask = <0 0 0 7>;
+ interrupt-map = <0 0 0 1 &pcie3_intc 0>,
+ <0 0 0 2 &pcie3_intc 1>,
+ <0 0 0 3 &pcie3_intc 2>,
+ <0 0 0 4 &pcie3_intc 3>;
marvell,pcie-port = <2>;
marvell,pcie-lane = <0>;
clocks = <&gateclk 6>;
status = "disabled";
+ pcie3_intc: interrupt-controller {
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ };
};
/*
@@ -129,16 +156,25 @@
reg = <0x2000 0 0 0 0>;
#address-cells = <3>;
#size-cells = <2>;
+ interrupt-names = "intx";
+ interrupts-extended = <&gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
#interrupt-cells = <1>;
ranges = <0x82000000 0 0 0x82000000 0x4 0 1 0
0x81000000 0 0 0x81000000 0x4 0 1 0>;
bus-range = <0x00 0xff>;
- interrupt-map-mask = <0 0 0 0>;
- interrupt-map = <0 0 0 0 &gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-map-mask = <0 0 0 7>;
+ interrupt-map = <0 0 0 1 &pcie4_intc 0>,
+ <0 0 0 2 &pcie4_intc 1>,
+ <0 0 0 3 &pcie4_intc 2>,
+ <0 0 0 4 &pcie4_intc 3>;
marvell,pcie-port = <3>;
marvell,pcie-lane = <0>;
clocks = <&gateclk 7>;
status = "disabled";
+ pcie4_intc: interrupt-controller {
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ };
};
};
};
--
2.20.1
On Wed, Jan 5, 2022 at 9:03 AM Pali Rohár <[email protected]> wrote:
>
> Split struct pci_ops between ops and child_ops. Member ops is used for
> accessing PCIe Root Ports via pci-bridge-emul.c driver and child_ops for
> accessing real PCIe cards.
>
> There is no need to mix these two struct pci_ops into one as PCI core code
> already provides separate callbacks via bridge->ops and bridge->child_ops.
I had similar patches including mvebu that I never got around to
sending out. I pushed the branch out now at least[1].
> Signed-off-by: Pali Rohár <[email protected]>
> ---
> drivers/pci/controller/pci-mvebu.c | 82 ++++++++++++++++--------------
> 1 file changed, 44 insertions(+), 38 deletions(-)
>
> diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> index 9ea2f6a7c2b0..1e90ab888075 100644
> --- a/drivers/pci/controller/pci-mvebu.c
> +++ b/drivers/pci/controller/pci-mvebu.c
> @@ -294,11 +294,29 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> mvebu_writel(port, mask, PCIE_MASK_OFF);
> }
>
> -static int mvebu_pcie_hw_rd_conf(struct mvebu_pcie_port *port,
> - struct pci_bus *bus,
> - u32 devfn, int where, int size, u32 *val)
> +static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
> + struct pci_bus *bus,
> + int devfn);
> +
> +static int mvebu_pcie_child_rd_conf(struct pci_bus *bus, u32 devfn, int where,
> + int size, u32 *val)
> {
> - void __iomem *conf_data = port->base + PCIE_CONF_DATA_OFF;
> + struct mvebu_pcie *pcie = bus->sysdata;
> + struct mvebu_pcie_port *port;
> + void __iomem *conf_data;
> +
> + port = mvebu_pcie_find_port(pcie, bus, devfn);
> + if (!port) {
> + *val = 0xffffffff;
> + return PCIBIOS_DEVICE_NOT_FOUND;
> + }
> +
> + if (!mvebu_pcie_link_up(port)) {
> + *val = 0xffffffff;
> + return PCIBIOS_DEVICE_NOT_FOUND;
> + }
> +
> + conf_data = port->base + PCIE_CONF_DATA_OFF;
>
> mvebu_writel(port, PCIE_CONF_ADDR(bus->number, devfn, where),
> PCIE_CONF_ADDR_OFF);
> @@ -321,11 +339,21 @@ static int mvebu_pcie_hw_rd_conf(struct mvebu_pcie_port *port,
> return PCIBIOS_SUCCESSFUL;
> }
>
> -static int mvebu_pcie_hw_wr_conf(struct mvebu_pcie_port *port,
> - struct pci_bus *bus,
> - u32 devfn, int where, int size, u32 val)
> +static int mvebu_pcie_child_wr_conf(struct pci_bus *bus, u32 devfn,
> + int where, int size, u32 val)
> {
> - void __iomem *conf_data = port->base + PCIE_CONF_DATA_OFF;
> + struct mvebu_pcie *pcie = bus->sysdata;
> + struct mvebu_pcie_port *port;
> + void __iomem *conf_data;
> +
> + port = mvebu_pcie_find_port(pcie, bus, devfn);
> + if (!port)
> + return PCIBIOS_DEVICE_NOT_FOUND;
> +
> + if (!mvebu_pcie_link_up(port))
> + return PCIBIOS_DEVICE_NOT_FOUND;
> +
> + conf_data = port->base + PCIE_CONF_DATA_OFF;
The same code in read and write is a hint to use .map_bus().
>
> mvebu_writel(port, PCIE_CONF_ADDR(bus->number, devfn, where),
> PCIE_CONF_ADDR_OFF);
> @@ -347,6 +375,11 @@ static int mvebu_pcie_hw_wr_conf(struct mvebu_pcie_port *port,
> return PCIBIOS_SUCCESSFUL;
> }
>
> +static struct pci_ops mvebu_pcie_child_ops = {
> + .read = mvebu_pcie_child_rd_conf,
> + .write = mvebu_pcie_child_wr_conf,
> +};
> +
> /*
> * Remove windows, starting from the largest ones to the smallest
> * ones.
> @@ -862,25 +895,12 @@ static int mvebu_pcie_wr_conf(struct pci_bus *bus, u32 devfn,
> {
> struct mvebu_pcie *pcie = bus->sysdata;
> struct mvebu_pcie_port *port;
> - int ret;
>
> port = mvebu_pcie_find_port(pcie, bus, devfn);
> if (!port)
> return PCIBIOS_DEVICE_NOT_FOUND;
It would be nice to go from 'bus' to 'bridge' ptr directly, but I
still had this in my version. I guess a standard RP struct as part of
decoupling host bridges from RPs would solve this issue.
Rob
[1] https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git/log/?h=pci-child-bus-ops
On Wednesday 05 January 2022 09:41:51 Rob Herring wrote:
> > @@ -347,6 +375,11 @@ static int mvebu_pcie_hw_wr_conf(struct mvebu_pcie_port *port,
> > return PCIBIOS_SUCCESSFUL;
> > }
> >
> > +static struct pci_ops mvebu_pcie_child_ops = {
> > + .read = mvebu_pcie_child_rd_conf,
> > + .write = mvebu_pcie_child_wr_conf,
> > +};
> > +
> > /*
> > * Remove windows, starting from the largest ones to the smallest
> > * ones.
> > @@ -862,25 +895,12 @@ static int mvebu_pcie_wr_conf(struct pci_bus *bus, u32 devfn,
> > {
> > struct mvebu_pcie *pcie = bus->sysdata;
> > struct mvebu_pcie_port *port;
> > - int ret;
> >
> > port = mvebu_pcie_find_port(pcie, bus, devfn);
> > if (!port)
> > return PCIBIOS_DEVICE_NOT_FOUND;
>
> It would be nice to go from 'bus' to 'bridge' ptr directly, but I
> still had this in my version. I guess a standard RP struct as part of
> decoupling host bridges from RPs would solve this issue.
Hello!
The problem is somewhere else. This driver is misusing bus 0 for doing
non-transparent bus-bridging between multiple PCI domains by registering
roots ports across all domains into domain 0, bus 0. All details are
in this my patch which documents this strange driver behavior:
https://lore.kernel.org/linux-pci/[email protected]/
So the correct solution is is split these multidomain mixing and then
every domain would have exactly one root port (as it is designed in HW).
On Wed, 05 Jan 2022 15:02:38 +0000,
Pali Rohár <[email protected]> wrote:
>
> This adds support for legacy INTx interrupts received from other PCIe
> devices and which are reported by a new INTx irq chip.
>
> With this change, kernel can distinguish between INTA, INTB, INTC and INTD
> interrupts.
>
> Note that for this support, device tree files has to be properly adjusted
> to provide "interrupts" or "interrupts-extended" property with intx
> interrupt source, "interrupt-names" property with "intx" string and also
> 'interrupt-controller' subnode must be defined.
>
> If device tree files do not provide these nodes then driver would work as
> before.
>
> Signed-off-by: Pali Rohár <[email protected]>
> ---
> drivers/pci/controller/pci-mvebu.c | 182 +++++++++++++++++++++++++++--
> 1 file changed, 174 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> index 1e90ab888075..04bcdd7b7a6d 100644
> --- a/drivers/pci/controller/pci-mvebu.c
> +++ b/drivers/pci/controller/pci-mvebu.c
> @@ -54,9 +54,10 @@
> PCIE_CONF_ADDR_EN)
> #define PCIE_CONF_DATA_OFF 0x18fc
> #define PCIE_INT_CAUSE_OFF 0x1900
> +#define PCIE_INT_UNMASK_OFF 0x1910
> +#define PCIE_INT_INTX(i) BIT(24+i)
> #define PCIE_INT_PM_PME BIT(28)
> -#define PCIE_MASK_OFF 0x1910
> -#define PCIE_MASK_ENABLE_INTS 0x0f000000
> +#define PCIE_INT_ALL_MASK GENMASK(31, 0)
> #define PCIE_CTRL_OFF 0x1a00
> #define PCIE_CTRL_X1_MODE 0x0001
> #define PCIE_CTRL_RC_MODE BIT(1)
> @@ -110,6 +111,10 @@ struct mvebu_pcie_port {
> struct mvebu_pcie_window iowin;
> u32 saved_pcie_stat;
> struct resource regs;
> + struct irq_domain *intx_irq_domain;
> + struct irq_chip intx_irq_chip;
Why is this structure per port? It really should be global. Printing
the port number in the name isn't enough of a reason.
> + raw_spinlock_t irq_lock;
> + int intx_irq;
> };
>
> static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
> @@ -235,7 +240,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
>
> static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> {
> - u32 ctrl, lnkcap, cmd, dev_rev, mask;
> + u32 ctrl, lnkcap, cmd, dev_rev, unmask;
>
> /* Setup PCIe controller to Root Complex mode. */
> ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> @@ -288,10 +293,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> /* Point PCIe unit MBUS decode windows to DRAM space. */
> mvebu_pcie_setup_wins(port);
>
> - /* Enable interrupt lines A-D. */
> - mask = mvebu_readl(port, PCIE_MASK_OFF);
> - mask |= PCIE_MASK_ENABLE_INTS;
> - mvebu_writel(port, mask, PCIE_MASK_OFF);
> + /* Mask all interrupt sources. */
> + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> +
> + /* Clear all interrupt causes. */
> + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> +
> + if (port->intx_irq <= 0) {
> + /*
> + * When neither "summary" interrupt, nor "intx" interrupt was
> + * specified in DT then unmask all legacy INTx interrupts as in
> + * this case driver does not provide a way for masking and
> + * unmasking of individual legacy INTx interrupts. In this case
> + * all interrupts, including legacy INTx are reported via one
> + * shared GIC source and therefore kernel cannot distinguish
> + * which individual legacy INTx was triggered. These interrupts
> + * are shared, so it should not cause any issue. Just
> + * performance penalty as every PCIe interrupt handler needs to
> + * be called when some interrupt is triggered.
> + */
> + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> + unmask |= PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
> + PCIE_INT_INTX(2) | PCIE_INT_INTX(3);
> + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
Maybe worth printing a warning here, so that the user knows they are
on thin ice.
> + }
> }
>
> static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
> @@ -924,6 +949,109 @@ static struct pci_ops mvebu_pcie_ops = {
> .write = mvebu_pcie_wr_conf,
> };
>
> +static void mvebu_pcie_intx_irq_mask(struct irq_data *d)
> +{
> + struct mvebu_pcie_port *port = d->domain->host_data;
> + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> + unsigned long flags;
> + u32 unmask;
> +
> + raw_spin_lock_irqsave(&port->irq_lock, flags);
> + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> + unmask &= ~PCIE_INT_INTX(hwirq);
> + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> +}
> +
> +static void mvebu_pcie_intx_irq_unmask(struct irq_data *d)
> +{
> + struct mvebu_pcie_port *port = d->domain->host_data;
> + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> + unsigned long flags;
> + u32 unmask;
> +
> + raw_spin_lock_irqsave(&port->irq_lock, flags);
> + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> + unmask |= PCIE_INT_INTX(hwirq);
> + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> +}
> +
> +static int mvebu_pcie_intx_irq_map(struct irq_domain *h,
> + unsigned int virq, irq_hw_number_t hwirq)
> +{
> + struct mvebu_pcie_port *port = h->host_data;
> +
> + irq_set_status_flags(virq, IRQ_LEVEL);
> + irq_set_chip_and_handler(virq, &port->intx_irq_chip, handle_level_irq);
> + irq_set_chip_data(virq, port);
> +
> + return 0;
> +}
> +
> +static const struct irq_domain_ops mvebu_pcie_intx_irq_domain_ops = {
> + .map = mvebu_pcie_intx_irq_map,
> + .xlate = irq_domain_xlate_onecell,
> +};
> +
> +static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
> +{
> + struct device *dev = &port->pcie->pdev->dev;
> + struct device_node *pcie_intc_node;
> +
> + raw_spin_lock_init(&port->irq_lock);
> +
> + port->intx_irq_chip.name = devm_kasprintf(dev, GFP_KERNEL,
> + "mvebu-%s-INTx",
> + port->name);
That's exactly what I really don't want to see. It prevents sharing of
the irq_chip structure, and gets in the way of making it const in the
future. Yes, I know that some drivers do that. I can't fix those,
because /proc/interrupts is ABI. But I really don't want to see more
of these.
/sys/kernel/debug/irqs already has all the information you need, as it
will happily give you the domain name and the interrupt topology.
> + port->intx_irq_chip.irq_mask = mvebu_pcie_intx_irq_mask;
> + port->intx_irq_chip.irq_unmask = mvebu_pcie_intx_irq_unmask;
> +
> + pcie_intc_node = of_get_next_child(port->dn, NULL);
> + if (!pcie_intc_node) {
> + dev_err(dev, "No PCIe Intc node found for %s\n", port->name);
> + return -ENODEV;
> + }
> +
> + port->intx_irq_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX,
> + &mvebu_pcie_intx_irq_domain_ops,
> + port);
> + of_node_put(pcie_intc_node);
> + if (!port->intx_irq_domain) {
> + devm_kfree(dev, port->intx_irq_chip.name);
> + dev_err(dev, "Failed to get INTx IRQ domain for %s\n", port->name);
> + return -ENOMEM;
> + }
> +
> + return 0;
> +}
> +
> +static void mvebu_pcie_irq_handler(struct irq_desc *desc)
> +{
> + struct mvebu_pcie_port *port = irq_desc_get_handler_data(desc);
> + struct irq_chip *chip = irq_desc_get_chip(desc);
> + struct device *dev = &port->pcie->pdev->dev;
> + u32 cause, unmask, status;
> + int i;
> +
> + chained_irq_enter(chip, desc);
> +
> + cause = mvebu_readl(port, PCIE_INT_CAUSE_OFF);
> + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
Why do you need to read this? If the CAUSE register also returns the
masked interrupts that are pending, it may be worth keeping a shadow
copy of the this register, as you end-up having an extra MMIO read on
each and every interrupt, which can't be great for performance.
> + status = cause & unmask;
> +
> + /* Process legacy INTx interrupts */
> + for (i = 0; i < PCI_NUM_INTX; i++) {
> + if (!(status & PCIE_INT_INTX(i)))
> + continue;
> +
> + if (generic_handle_domain_irq(port->intx_irq_domain, i) == -EINVAL)
> + dev_err_ratelimited(dev, "unexpected INT%c IRQ\n", (char)i+'A');
> + }
> +
> + chained_irq_exit(chip, desc);
> +}
> +
> static int mvebu_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
> {
> /* Interrupt support on mvebu emulated bridges is not implemented yet */
> @@ -1121,6 +1249,16 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
> port->io_attr = -1;
> }
>
> + /*
> + * Old DT bindings do not contain "intx" interrupt
> + * so do not fail probing driver when interrupt does not exist.
> + */
> + port->intx_irq = of_irq_get_byname(child, "intx");
> + if (port->intx_irq == -EPROBE_DEFER) {
> + ret = port->intx_irq;
> + goto err;
> + }
> +
> reset_gpio = of_get_named_gpio_flags(child, "reset-gpios", 0, &flags);
> if (reset_gpio == -EPROBE_DEFER) {
> ret = reset_gpio;
> @@ -1317,6 +1455,7 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
>
> for (i = 0; i < pcie->nports; i++) {
> struct mvebu_pcie_port *port = &pcie->ports[i];
> + int irq = port->intx_irq;
>
> child = port->dn;
> if (!child)
> @@ -1344,6 +1483,22 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
> continue;
> }
>
> + if (irq > 0) {
> + ret = mvebu_pcie_init_irq_domain(port);
> + if (ret) {
> + dev_err(dev, "%s: cannot init irq domain\n",
> + port->name);
> + pci_bridge_emul_cleanup(&port->bridge);
> + devm_iounmap(dev, port->base);
> + port->base = NULL;
> + mvebu_pcie_powerdown(port);
> + continue;
> + }
> + irq_set_chained_handler_and_data(irq,
> + mvebu_pcie_irq_handler,
> + port);
> + }
> +
> /*
> * PCIe topology exported by mvebu hw is quite complicated. In
> * reality has something like N fully independent host bridges
> @@ -1448,6 +1603,7 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
>
> for (i = 0; i < pcie->nports; i++) {
> struct mvebu_pcie_port *port = &pcie->ports[i];
> + int irq = port->intx_irq;
>
> if (!port->base)
> continue;
> @@ -1458,7 +1614,17 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
> mvebu_writel(port, cmd, PCIE_CMD_OFF);
>
> /* Mask all interrupt sources. */
> - mvebu_writel(port, 0, PCIE_MASK_OFF);
> + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> +
> + /* Clear all interrupt causes. */
> + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> +
> + /* Remove IRQ domains. */
> + if (port->intx_irq_domain)
> + irq_domain_remove(port->intx_irq_domain);
> +
> + if (irq > 0)
> + irq_set_chained_handler_and_data(irq, NULL, NULL);
>
> /* Free config space for emulated root bridge. */
> pci_bridge_emul_cleanup(&port->bridge);
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
On Thursday 06 January 2022 15:28:20 Marc Zyngier wrote:
> On Wed, 05 Jan 2022 15:02:38 +0000,
> Pali Rohár <[email protected]> wrote:
> >
> > This adds support for legacy INTx interrupts received from other PCIe
> > devices and which are reported by a new INTx irq chip.
> >
> > With this change, kernel can distinguish between INTA, INTB, INTC and INTD
> > interrupts.
> >
> > Note that for this support, device tree files has to be properly adjusted
> > to provide "interrupts" or "interrupts-extended" property with intx
> > interrupt source, "interrupt-names" property with "intx" string and also
> > 'interrupt-controller' subnode must be defined.
> >
> > If device tree files do not provide these nodes then driver would work as
> > before.
> >
> > Signed-off-by: Pali Rohár <[email protected]>
> > ---
> > drivers/pci/controller/pci-mvebu.c | 182 +++++++++++++++++++++++++++--
> > 1 file changed, 174 insertions(+), 8 deletions(-)
> >
> > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > index 1e90ab888075..04bcdd7b7a6d 100644
> > --- a/drivers/pci/controller/pci-mvebu.c
> > +++ b/drivers/pci/controller/pci-mvebu.c
> > @@ -54,9 +54,10 @@
> > PCIE_CONF_ADDR_EN)
> > #define PCIE_CONF_DATA_OFF 0x18fc
> > #define PCIE_INT_CAUSE_OFF 0x1900
> > +#define PCIE_INT_UNMASK_OFF 0x1910
> > +#define PCIE_INT_INTX(i) BIT(24+i)
> > #define PCIE_INT_PM_PME BIT(28)
> > -#define PCIE_MASK_OFF 0x1910
> > -#define PCIE_MASK_ENABLE_INTS 0x0f000000
> > +#define PCIE_INT_ALL_MASK GENMASK(31, 0)
> > #define PCIE_CTRL_OFF 0x1a00
> > #define PCIE_CTRL_X1_MODE 0x0001
> > #define PCIE_CTRL_RC_MODE BIT(1)
> > @@ -110,6 +111,10 @@ struct mvebu_pcie_port {
> > struct mvebu_pcie_window iowin;
> > u32 saved_pcie_stat;
> > struct resource regs;
> > + struct irq_domain *intx_irq_domain;
> > + struct irq_chip intx_irq_chip;
>
> Why is this structure per port? It really should be global. Printing
> the port number in the name isn't enough of a reason.
Because each port has its own independent set of INTA-INTD interrupts.
> > + raw_spinlock_t irq_lock;
> > + int intx_irq;
> > };
> >
> > static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
> > @@ -235,7 +240,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
> >
> > static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > {
> > - u32 ctrl, lnkcap, cmd, dev_rev, mask;
> > + u32 ctrl, lnkcap, cmd, dev_rev, unmask;
> >
> > /* Setup PCIe controller to Root Complex mode. */
> > ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> > @@ -288,10 +293,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > /* Point PCIe unit MBUS decode windows to DRAM space. */
> > mvebu_pcie_setup_wins(port);
> >
> > - /* Enable interrupt lines A-D. */
> > - mask = mvebu_readl(port, PCIE_MASK_OFF);
> > - mask |= PCIE_MASK_ENABLE_INTS;
> > - mvebu_writel(port, mask, PCIE_MASK_OFF);
> > + /* Mask all interrupt sources. */
> > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > +
> > + /* Clear all interrupt causes. */
> > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > +
> > + if (port->intx_irq <= 0) {
> > + /*
> > + * When neither "summary" interrupt, nor "intx" interrupt was
> > + * specified in DT then unmask all legacy INTx interrupts as in
> > + * this case driver does not provide a way for masking and
> > + * unmasking of individual legacy INTx interrupts. In this case
> > + * all interrupts, including legacy INTx are reported via one
> > + * shared GIC source and therefore kernel cannot distinguish
> > + * which individual legacy INTx was triggered. These interrupts
> > + * are shared, so it should not cause any issue. Just
> > + * performance penalty as every PCIe interrupt handler needs to
> > + * be called when some interrupt is triggered.
> > + */
> > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > + unmask |= PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
> > + PCIE_INT_INTX(2) | PCIE_INT_INTX(3);
> > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
>
> Maybe worth printing a warning here, so that the user knows they are
> on thin ice.
Ok. I can add it here. Anyway, this is default current state without
this patch.
> > + }
> > }
> >
> > static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
> > @@ -924,6 +949,109 @@ static struct pci_ops mvebu_pcie_ops = {
> > .write = mvebu_pcie_wr_conf,
> > };
> >
> > +static void mvebu_pcie_intx_irq_mask(struct irq_data *d)
> > +{
> > + struct mvebu_pcie_port *port = d->domain->host_data;
> > + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> > + unsigned long flags;
> > + u32 unmask;
> > +
> > + raw_spin_lock_irqsave(&port->irq_lock, flags);
> > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > + unmask &= ~PCIE_INT_INTX(hwirq);
> > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> > +}
> > +
> > +static void mvebu_pcie_intx_irq_unmask(struct irq_data *d)
> > +{
> > + struct mvebu_pcie_port *port = d->domain->host_data;
> > + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> > + unsigned long flags;
> > + u32 unmask;
> > +
> > + raw_spin_lock_irqsave(&port->irq_lock, flags);
> > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > + unmask |= PCIE_INT_INTX(hwirq);
> > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> > +}
> > +
> > +static int mvebu_pcie_intx_irq_map(struct irq_domain *h,
> > + unsigned int virq, irq_hw_number_t hwirq)
> > +{
> > + struct mvebu_pcie_port *port = h->host_data;
> > +
> > + irq_set_status_flags(virq, IRQ_LEVEL);
> > + irq_set_chip_and_handler(virq, &port->intx_irq_chip, handle_level_irq);
> > + irq_set_chip_data(virq, port);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct irq_domain_ops mvebu_pcie_intx_irq_domain_ops = {
> > + .map = mvebu_pcie_intx_irq_map,
> > + .xlate = irq_domain_xlate_onecell,
> > +};
> > +
> > +static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
> > +{
> > + struct device *dev = &port->pcie->pdev->dev;
> > + struct device_node *pcie_intc_node;
> > +
> > + raw_spin_lock_init(&port->irq_lock);
> > +
> > + port->intx_irq_chip.name = devm_kasprintf(dev, GFP_KERNEL,
> > + "mvebu-%s-INTx",
> > + port->name);
>
> That's exactly what I really don't want to see. It prevents sharing of
> the irq_chip structure, and gets in the way of making it const in the
> future. Yes, I know that some drivers do that. I can't fix those,
> because /proc/interrupts is ABI. But I really don't want to see more
> of these.
Well, I do not understand why it should be shared and with who. HW has N
independent IRQ chips for legacy interrupts. And each one will be
specified in DT per HW layout / design.
> /sys/kernel/debug/irqs already has all the information you need, as it
> will happily give you the domain name and the interrupt topology.
>
> > + port->intx_irq_chip.irq_mask = mvebu_pcie_intx_irq_mask;
> > + port->intx_irq_chip.irq_unmask = mvebu_pcie_intx_irq_unmask;
> > +
> > + pcie_intc_node = of_get_next_child(port->dn, NULL);
> > + if (!pcie_intc_node) {
> > + dev_err(dev, "No PCIe Intc node found for %s\n", port->name);
> > + return -ENODEV;
> > + }
> > +
> > + port->intx_irq_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX,
> > + &mvebu_pcie_intx_irq_domain_ops,
> > + port);
> > + of_node_put(pcie_intc_node);
> > + if (!port->intx_irq_domain) {
> > + devm_kfree(dev, port->intx_irq_chip.name);
> > + dev_err(dev, "Failed to get INTx IRQ domain for %s\n", port->name);
> > + return -ENOMEM;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void mvebu_pcie_irq_handler(struct irq_desc *desc)
> > +{
> > + struct mvebu_pcie_port *port = irq_desc_get_handler_data(desc);
> > + struct irq_chip *chip = irq_desc_get_chip(desc);
> > + struct device *dev = &port->pcie->pdev->dev;
> > + u32 cause, unmask, status;
> > + int i;
> > +
> > + chained_irq_enter(chip, desc);
> > +
> > + cause = mvebu_readl(port, PCIE_INT_CAUSE_OFF);
> > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
>
> Why do you need to read this? If the CAUSE register also returns the
> masked interrupts that are pending,
Yes, it returns all interrupts, even those which are masked for which is
not system interested.
> it may be worth keeping a shadow
> copy of the this register, as you end-up having an extra MMIO read on
> each and every interrupt, which can't be great for performance.
Ok. I can set shadow copy of unmask register into port structure. But it
has really performance impact when reading directly unmask register from
hw and when reading it from port shadow copy structure?
> > + status = cause & unmask;
> > +
> > + /* Process legacy INTx interrupts */
> > + for (i = 0; i < PCI_NUM_INTX; i++) {
> > + if (!(status & PCIE_INT_INTX(i)))
> > + continue;
> > +
> > + if (generic_handle_domain_irq(port->intx_irq_domain, i) == -EINVAL)
> > + dev_err_ratelimited(dev, "unexpected INT%c IRQ\n", (char)i+'A');
> > + }
> > +
> > + chained_irq_exit(chip, desc);
> > +}
> > +
> > static int mvebu_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
> > {
> > /* Interrupt support on mvebu emulated bridges is not implemented yet */
> > @@ -1121,6 +1249,16 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
> > port->io_attr = -1;
> > }
> >
> > + /*
> > + * Old DT bindings do not contain "intx" interrupt
> > + * so do not fail probing driver when interrupt does not exist.
> > + */
> > + port->intx_irq = of_irq_get_byname(child, "intx");
> > + if (port->intx_irq == -EPROBE_DEFER) {
> > + ret = port->intx_irq;
> > + goto err;
> > + }
> > +
> > reset_gpio = of_get_named_gpio_flags(child, "reset-gpios", 0, &flags);
> > if (reset_gpio == -EPROBE_DEFER) {
> > ret = reset_gpio;
> > @@ -1317,6 +1455,7 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
> >
> > for (i = 0; i < pcie->nports; i++) {
> > struct mvebu_pcie_port *port = &pcie->ports[i];
> > + int irq = port->intx_irq;
> >
> > child = port->dn;
> > if (!child)
> > @@ -1344,6 +1483,22 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
> > continue;
> > }
> >
> > + if (irq > 0) {
> > + ret = mvebu_pcie_init_irq_domain(port);
> > + if (ret) {
> > + dev_err(dev, "%s: cannot init irq domain\n",
> > + port->name);
> > + pci_bridge_emul_cleanup(&port->bridge);
> > + devm_iounmap(dev, port->base);
> > + port->base = NULL;
> > + mvebu_pcie_powerdown(port);
> > + continue;
> > + }
> > + irq_set_chained_handler_and_data(irq,
> > + mvebu_pcie_irq_handler,
> > + port);
> > + }
> > +
> > /*
> > * PCIe topology exported by mvebu hw is quite complicated. In
> > * reality has something like N fully independent host bridges
> > @@ -1448,6 +1603,7 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
> >
> > for (i = 0; i < pcie->nports; i++) {
> > struct mvebu_pcie_port *port = &pcie->ports[i];
> > + int irq = port->intx_irq;
> >
> > if (!port->base)
> > continue;
> > @@ -1458,7 +1614,17 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
> > mvebu_writel(port, cmd, PCIE_CMD_OFF);
> >
> > /* Mask all interrupt sources. */
> > - mvebu_writel(port, 0, PCIE_MASK_OFF);
> > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > +
> > + /* Clear all interrupt causes. */
> > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > +
> > + /* Remove IRQ domains. */
> > + if (port->intx_irq_domain)
> > + irq_domain_remove(port->intx_irq_domain);
> > +
> > + if (irq > 0)
> > + irq_set_chained_handler_and_data(irq, NULL, NULL);
> >
> > /* Free config space for emulated root bridge. */
> > pci_bridge_emul_cleanup(&port->bridge);
>
> Thanks,
>
> M.
>
> --
> Without deviation from the norm, progress is not possible.
On Thu, 06 Jan 2022 15:44:47 +0000,
Pali Rohár <[email protected]> wrote:
>
> On Thursday 06 January 2022 15:28:20 Marc Zyngier wrote:
> > On Wed, 05 Jan 2022 15:02:38 +0000,
> > Pali Rohár <[email protected]> wrote:
> > >
> > > This adds support for legacy INTx interrupts received from other PCIe
> > > devices and which are reported by a new INTx irq chip.
> > >
> > > With this change, kernel can distinguish between INTA, INTB, INTC and INTD
> > > interrupts.
> > >
> > > Note that for this support, device tree files has to be properly adjusted
> > > to provide "interrupts" or "interrupts-extended" property with intx
> > > interrupt source, "interrupt-names" property with "intx" string and also
> > > 'interrupt-controller' subnode must be defined.
> > >
> > > If device tree files do not provide these nodes then driver would work as
> > > before.
> > >
> > > Signed-off-by: Pali Rohár <[email protected]>
> > > ---
> > > drivers/pci/controller/pci-mvebu.c | 182 +++++++++++++++++++++++++++--
> > > 1 file changed, 174 insertions(+), 8 deletions(-)
> > >
> > > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > > index 1e90ab888075..04bcdd7b7a6d 100644
> > > --- a/drivers/pci/controller/pci-mvebu.c
> > > +++ b/drivers/pci/controller/pci-mvebu.c
> > > @@ -54,9 +54,10 @@
> > > PCIE_CONF_ADDR_EN)
> > > #define PCIE_CONF_DATA_OFF 0x18fc
> > > #define PCIE_INT_CAUSE_OFF 0x1900
> > > +#define PCIE_INT_UNMASK_OFF 0x1910
> > > +#define PCIE_INT_INTX(i) BIT(24+i)
> > > #define PCIE_INT_PM_PME BIT(28)
> > > -#define PCIE_MASK_OFF 0x1910
> > > -#define PCIE_MASK_ENABLE_INTS 0x0f000000
> > > +#define PCIE_INT_ALL_MASK GENMASK(31, 0)
> > > #define PCIE_CTRL_OFF 0x1a00
> > > #define PCIE_CTRL_X1_MODE 0x0001
> > > #define PCIE_CTRL_RC_MODE BIT(1)
> > > @@ -110,6 +111,10 @@ struct mvebu_pcie_port {
> > > struct mvebu_pcie_window iowin;
> > > u32 saved_pcie_stat;
> > > struct resource regs;
> > > + struct irq_domain *intx_irq_domain;
> > > + struct irq_chip intx_irq_chip;
> >
> > Why is this structure per port? It really should be global. Printing
> > the port number in the name isn't enough of a reason.
>
> Because each port has its own independent set of INTA-INTD
> interrupts.
That doesn't warrant a copy of an irq_chip structure that contains the
exact same callbacks, and only differs by *a string*. And the use of
this string is only to end-up in /proc/interrupts, which is totally
pointless.
>
> > > + raw_spinlock_t irq_lock;
> > > + int intx_irq;
> > > };
> > >
> > > static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
> > > @@ -235,7 +240,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
> > >
> > > static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > {
> > > - u32 ctrl, lnkcap, cmd, dev_rev, mask;
> > > + u32 ctrl, lnkcap, cmd, dev_rev, unmask;
> > >
> > > /* Setup PCIe controller to Root Complex mode. */
> > > ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> > > @@ -288,10 +293,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > /* Point PCIe unit MBUS decode windows to DRAM space. */
> > > mvebu_pcie_setup_wins(port);
> > >
> > > - /* Enable interrupt lines A-D. */
> > > - mask = mvebu_readl(port, PCIE_MASK_OFF);
> > > - mask |= PCIE_MASK_ENABLE_INTS;
> > > - mvebu_writel(port, mask, PCIE_MASK_OFF);
> > > + /* Mask all interrupt sources. */
> > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > > +
> > > + /* Clear all interrupt causes. */
> > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > > +
> > > + if (port->intx_irq <= 0) {
> > > + /*
> > > + * When neither "summary" interrupt, nor "intx" interrupt was
> > > + * specified in DT then unmask all legacy INTx interrupts as in
> > > + * this case driver does not provide a way for masking and
> > > + * unmasking of individual legacy INTx interrupts. In this case
> > > + * all interrupts, including legacy INTx are reported via one
> > > + * shared GIC source and therefore kernel cannot distinguish
> > > + * which individual legacy INTx was triggered. These interrupts
> > > + * are shared, so it should not cause any issue. Just
> > > + * performance penalty as every PCIe interrupt handler needs to
> > > + * be called when some interrupt is triggered.
> > > + */
> > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > + unmask |= PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
> > > + PCIE_INT_INTX(2) | PCIE_INT_INTX(3);
> > > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> >
> > Maybe worth printing a warning here, so that the user knows they are
> > on thin ice.
>
> Ok. I can add it here. Anyway, this is default current state without
> this patch.
>
> > > + }
> > > }
> > >
> > > static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
> > > @@ -924,6 +949,109 @@ static struct pci_ops mvebu_pcie_ops = {
> > > .write = mvebu_pcie_wr_conf,
> > > };
> > >
> > > +static void mvebu_pcie_intx_irq_mask(struct irq_data *d)
> > > +{
> > > + struct mvebu_pcie_port *port = d->domain->host_data;
> > > + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> > > + unsigned long flags;
> > > + u32 unmask;
> > > +
> > > + raw_spin_lock_irqsave(&port->irq_lock, flags);
> > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > + unmask &= ~PCIE_INT_INTX(hwirq);
> > > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > > + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> > > +}
> > > +
> > > +static void mvebu_pcie_intx_irq_unmask(struct irq_data *d)
> > > +{
> > > + struct mvebu_pcie_port *port = d->domain->host_data;
> > > + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> > > + unsigned long flags;
> > > + u32 unmask;
> > > +
> > > + raw_spin_lock_irqsave(&port->irq_lock, flags);
> > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > + unmask |= PCIE_INT_INTX(hwirq);
> > > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > > + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> > > +}
> > > +
> > > +static int mvebu_pcie_intx_irq_map(struct irq_domain *h,
> > > + unsigned int virq, irq_hw_number_t hwirq)
> > > +{
> > > + struct mvebu_pcie_port *port = h->host_data;
> > > +
> > > + irq_set_status_flags(virq, IRQ_LEVEL);
> > > + irq_set_chip_and_handler(virq, &port->intx_irq_chip, handle_level_irq);
> > > + irq_set_chip_data(virq, port);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static const struct irq_domain_ops mvebu_pcie_intx_irq_domain_ops = {
> > > + .map = mvebu_pcie_intx_irq_map,
> > > + .xlate = irq_domain_xlate_onecell,
> > > +};
> > > +
> > > +static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
> > > +{
> > > + struct device *dev = &port->pcie->pdev->dev;
> > > + struct device_node *pcie_intc_node;
> > > +
> > > + raw_spin_lock_init(&port->irq_lock);
> > > +
> > > + port->intx_irq_chip.name = devm_kasprintf(dev, GFP_KERNEL,
> > > + "mvebu-%s-INTx",
> > > + port->name);
> >
> > That's exactly what I really don't want to see. It prevents sharing of
> > the irq_chip structure, and gets in the way of making it const in the
> > future. Yes, I know that some drivers do that. I can't fix those,
> > because /proc/interrupts is ABI. But I really don't want to see more
> > of these.
>
> Well, I do not understand why it should be shared and with who. HW has N
> independent IRQ chips for legacy interrupts. And each one will be
> specified in DT per HW layout / design.
If you have multiple ports, all the ports can share the irq_chip
structure. Actually scratch that. They *MUST* share the structure. The
only reason you're not sharing it is to be able to print this useless
string in /proc/interrupts.
M.
--
Without deviation from the norm, progress is not possible.
On Thursday 06 January 2022 15:55:11 Marc Zyngier wrote:
> On Thu, 06 Jan 2022 15:44:47 +0000,
> Pali Rohár <[email protected]> wrote:
> >
> > On Thursday 06 January 2022 15:28:20 Marc Zyngier wrote:
> > > On Wed, 05 Jan 2022 15:02:38 +0000,
> > > Pali Rohár <[email protected]> wrote:
> > > >
> > > > This adds support for legacy INTx interrupts received from other PCIe
> > > > devices and which are reported by a new INTx irq chip.
> > > >
> > > > With this change, kernel can distinguish between INTA, INTB, INTC and INTD
> > > > interrupts.
> > > >
> > > > Note that for this support, device tree files has to be properly adjusted
> > > > to provide "interrupts" or "interrupts-extended" property with intx
> > > > interrupt source, "interrupt-names" property with "intx" string and also
> > > > 'interrupt-controller' subnode must be defined.
> > > >
> > > > If device tree files do not provide these nodes then driver would work as
> > > > before.
> > > >
> > > > Signed-off-by: Pali Rohár <[email protected]>
> > > > ---
> > > > drivers/pci/controller/pci-mvebu.c | 182 +++++++++++++++++++++++++++--
> > > > 1 file changed, 174 insertions(+), 8 deletions(-)
> > > >
> > > > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > > > index 1e90ab888075..04bcdd7b7a6d 100644
> > > > --- a/drivers/pci/controller/pci-mvebu.c
> > > > +++ b/drivers/pci/controller/pci-mvebu.c
> > > > @@ -54,9 +54,10 @@
> > > > PCIE_CONF_ADDR_EN)
> > > > #define PCIE_CONF_DATA_OFF 0x18fc
> > > > #define PCIE_INT_CAUSE_OFF 0x1900
> > > > +#define PCIE_INT_UNMASK_OFF 0x1910
> > > > +#define PCIE_INT_INTX(i) BIT(24+i)
> > > > #define PCIE_INT_PM_PME BIT(28)
> > > > -#define PCIE_MASK_OFF 0x1910
> > > > -#define PCIE_MASK_ENABLE_INTS 0x0f000000
> > > > +#define PCIE_INT_ALL_MASK GENMASK(31, 0)
> > > > #define PCIE_CTRL_OFF 0x1a00
> > > > #define PCIE_CTRL_X1_MODE 0x0001
> > > > #define PCIE_CTRL_RC_MODE BIT(1)
> > > > @@ -110,6 +111,10 @@ struct mvebu_pcie_port {
> > > > struct mvebu_pcie_window iowin;
> > > > u32 saved_pcie_stat;
> > > > struct resource regs;
> > > > + struct irq_domain *intx_irq_domain;
> > > > + struct irq_chip intx_irq_chip;
> > >
> > > Why is this structure per port? It really should be global. Printing
> > > the port number in the name isn't enough of a reason.
> >
> > Because each port has its own independent set of INTA-INTD
> > interrupts.
>
> That doesn't warrant a copy of an irq_chip structure that contains the
> exact same callbacks, and only differs by *a string*. And the use of
> this string is only to end-up in /proc/interrupts, which is totally
> pointless.
>
> >
> > > > + raw_spinlock_t irq_lock;
> > > > + int intx_irq;
> > > > };
> > > >
> > > > static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
> > > > @@ -235,7 +240,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
> > > >
> > > > static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > {
> > > > - u32 ctrl, lnkcap, cmd, dev_rev, mask;
> > > > + u32 ctrl, lnkcap, cmd, dev_rev, unmask;
> > > >
> > > > /* Setup PCIe controller to Root Complex mode. */
> > > > ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> > > > @@ -288,10 +293,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > /* Point PCIe unit MBUS decode windows to DRAM space. */
> > > > mvebu_pcie_setup_wins(port);
> > > >
> > > > - /* Enable interrupt lines A-D. */
> > > > - mask = mvebu_readl(port, PCIE_MASK_OFF);
> > > > - mask |= PCIE_MASK_ENABLE_INTS;
> > > > - mvebu_writel(port, mask, PCIE_MASK_OFF);
> > > > + /* Mask all interrupt sources. */
> > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > > > +
> > > > + /* Clear all interrupt causes. */
> > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > > > +
> > > > + if (port->intx_irq <= 0) {
> > > > + /*
> > > > + * When neither "summary" interrupt, nor "intx" interrupt was
> > > > + * specified in DT then unmask all legacy INTx interrupts as in
> > > > + * this case driver does not provide a way for masking and
> > > > + * unmasking of individual legacy INTx interrupts. In this case
> > > > + * all interrupts, including legacy INTx are reported via one
> > > > + * shared GIC source and therefore kernel cannot distinguish
> > > > + * which individual legacy INTx was triggered. These interrupts
> > > > + * are shared, so it should not cause any issue. Just
> > > > + * performance penalty as every PCIe interrupt handler needs to
> > > > + * be called when some interrupt is triggered.
> > > > + */
> > > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > > + unmask |= PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
> > > > + PCIE_INT_INTX(2) | PCIE_INT_INTX(3);
> > > > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > >
> > > Maybe worth printing a warning here, so that the user knows they are
> > > on thin ice.
> >
> > Ok. I can add it here. Anyway, this is default current state without
> > this patch.
> >
> > > > + }
> > > > }
> > > >
> > > > static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
> > > > @@ -924,6 +949,109 @@ static struct pci_ops mvebu_pcie_ops = {
> > > > .write = mvebu_pcie_wr_conf,
> > > > };
> > > >
> > > > +static void mvebu_pcie_intx_irq_mask(struct irq_data *d)
> > > > +{
> > > > + struct mvebu_pcie_port *port = d->domain->host_data;
> > > > + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> > > > + unsigned long flags;
> > > > + u32 unmask;
> > > > +
> > > > + raw_spin_lock_irqsave(&port->irq_lock, flags);
> > > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > > + unmask &= ~PCIE_INT_INTX(hwirq);
> > > > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > > > + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> > > > +}
> > > > +
> > > > +static void mvebu_pcie_intx_irq_unmask(struct irq_data *d)
> > > > +{
> > > > + struct mvebu_pcie_port *port = d->domain->host_data;
> > > > + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> > > > + unsigned long flags;
> > > > + u32 unmask;
> > > > +
> > > > + raw_spin_lock_irqsave(&port->irq_lock, flags);
> > > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > > + unmask |= PCIE_INT_INTX(hwirq);
> > > > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > > > + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> > > > +}
> > > > +
> > > > +static int mvebu_pcie_intx_irq_map(struct irq_domain *h,
> > > > + unsigned int virq, irq_hw_number_t hwirq)
> > > > +{
> > > > + struct mvebu_pcie_port *port = h->host_data;
> > > > +
> > > > + irq_set_status_flags(virq, IRQ_LEVEL);
> > > > + irq_set_chip_and_handler(virq, &port->intx_irq_chip, handle_level_irq);
> > > > + irq_set_chip_data(virq, port);
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static const struct irq_domain_ops mvebu_pcie_intx_irq_domain_ops = {
> > > > + .map = mvebu_pcie_intx_irq_map,
> > > > + .xlate = irq_domain_xlate_onecell,
> > > > +};
> > > > +
> > > > +static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
> > > > +{
> > > > + struct device *dev = &port->pcie->pdev->dev;
> > > > + struct device_node *pcie_intc_node;
> > > > +
> > > > + raw_spin_lock_init(&port->irq_lock);
> > > > +
> > > > + port->intx_irq_chip.name = devm_kasprintf(dev, GFP_KERNEL,
> > > > + "mvebu-%s-INTx",
> > > > + port->name);
> > >
> > > That's exactly what I really don't want to see. It prevents sharing of
> > > the irq_chip structure, and gets in the way of making it const in the
> > > future. Yes, I know that some drivers do that. I can't fix those,
> > > because /proc/interrupts is ABI. But I really don't want to see more
> > > of these.
> >
> > Well, I do not understand why it should be shared and with who. HW has N
> > independent IRQ chips for legacy interrupts. And each one will be
> > specified in DT per HW layout / design.
>
> If you have multiple ports, all the ports can share the irq_chip
> structure. Actually scratch that. They *MUST* share the structure. The
> only reason you're not sharing it is to be able to print this useless
> string in /proc/interrupts.
What is the point of sharing one irq chip if HW has N independent irq
chips (for legacy interrupts)? I do not catch it yet. And I do not care
here for /proc/interrupts, so also I have not caught what do you mean be
last sentence with "the only reason".
And I still do not see how it could even work to have just one irq chip
and one irq domain as each irq domain needs to know to which port it
belongs, so it can mask/unmask interrupts from correct port. Also
initialization of domain is taking DT node and for each port it is
different.
So I'm somehow confused here...
The improvement in this patch is to be able to mask INTA interrupts on
port 1 and let INTA interrupts unmasked on port 2 if there drivers are
interested only for interrupts from device connected to port 2.
And if all interrupts are going to be shared (again) then it does not
solve any problem.
On Thu, 06 Jan 2022 16:20:47 +0000,
Pali Rohár <[email protected]> wrote:
>
> On Thursday 06 January 2022 15:55:11 Marc Zyngier wrote:
> > On Thu, 06 Jan 2022 15:44:47 +0000,
> > Pali Rohár <[email protected]> wrote:
> > >
> > > On Thursday 06 January 2022 15:28:20 Marc Zyngier wrote:
> > > > On Wed, 05 Jan 2022 15:02:38 +0000,
> > > > Pali Rohár <[email protected]> wrote:
> > > > >
> > > > > This adds support for legacy INTx interrupts received from other PCIe
> > > > > devices and which are reported by a new INTx irq chip.
> > > > >
> > > > > With this change, kernel can distinguish between INTA, INTB, INTC and INTD
> > > > > interrupts.
> > > > >
> > > > > Note that for this support, device tree files has to be properly adjusted
> > > > > to provide "interrupts" or "interrupts-extended" property with intx
> > > > > interrupt source, "interrupt-names" property with "intx" string and also
> > > > > 'interrupt-controller' subnode must be defined.
> > > > >
> > > > > If device tree files do not provide these nodes then driver would work as
> > > > > before.
> > > > >
> > > > > Signed-off-by: Pali Rohár <[email protected]>
> > > > > ---
> > > > > drivers/pci/controller/pci-mvebu.c | 182 +++++++++++++++++++++++++++--
> > > > > 1 file changed, 174 insertions(+), 8 deletions(-)
> > > > >
> > > > > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > > > > index 1e90ab888075..04bcdd7b7a6d 100644
> > > > > --- a/drivers/pci/controller/pci-mvebu.c
> > > > > +++ b/drivers/pci/controller/pci-mvebu.c
> > > > > @@ -54,9 +54,10 @@
> > > > > PCIE_CONF_ADDR_EN)
> > > > > #define PCIE_CONF_DATA_OFF 0x18fc
> > > > > #define PCIE_INT_CAUSE_OFF 0x1900
> > > > > +#define PCIE_INT_UNMASK_OFF 0x1910
> > > > > +#define PCIE_INT_INTX(i) BIT(24+i)
> > > > > #define PCIE_INT_PM_PME BIT(28)
> > > > > -#define PCIE_MASK_OFF 0x1910
> > > > > -#define PCIE_MASK_ENABLE_INTS 0x0f000000
> > > > > +#define PCIE_INT_ALL_MASK GENMASK(31, 0)
> > > > > #define PCIE_CTRL_OFF 0x1a00
> > > > > #define PCIE_CTRL_X1_MODE 0x0001
> > > > > #define PCIE_CTRL_RC_MODE BIT(1)
> > > > > @@ -110,6 +111,10 @@ struct mvebu_pcie_port {
> > > > > struct mvebu_pcie_window iowin;
> > > > > u32 saved_pcie_stat;
> > > > > struct resource regs;
> > > > > + struct irq_domain *intx_irq_domain;
> > > > > + struct irq_chip intx_irq_chip;
> > > >
> > > > Why is this structure per port? It really should be global. Printing
> > > > the port number in the name isn't enough of a reason.
> > >
> > > Because each port has its own independent set of INTA-INTD
> > > interrupts.
> >
> > That doesn't warrant a copy of an irq_chip structure that contains the
> > exact same callbacks, and only differs by *a string*. And the use of
> > this string is only to end-up in /proc/interrupts, which is totally
> > pointless.
> >
> > >
> > > > > + raw_spinlock_t irq_lock;
> > > > > + int intx_irq;
> > > > > };
> > > > >
> > > > > static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
> > > > > @@ -235,7 +240,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
> > > > >
> > > > > static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > > {
> > > > > - u32 ctrl, lnkcap, cmd, dev_rev, mask;
> > > > > + u32 ctrl, lnkcap, cmd, dev_rev, unmask;
> > > > >
> > > > > /* Setup PCIe controller to Root Complex mode. */
> > > > > ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> > > > > @@ -288,10 +293,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > > /* Point PCIe unit MBUS decode windows to DRAM space. */
> > > > > mvebu_pcie_setup_wins(port);
> > > > >
> > > > > - /* Enable interrupt lines A-D. */
> > > > > - mask = mvebu_readl(port, PCIE_MASK_OFF);
> > > > > - mask |= PCIE_MASK_ENABLE_INTS;
> > > > > - mvebu_writel(port, mask, PCIE_MASK_OFF);
> > > > > + /* Mask all interrupt sources. */
> > > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > > > > +
> > > > > + /* Clear all interrupt causes. */
> > > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > > > > +
> > > > > + if (port->intx_irq <= 0) {
> > > > > + /*
> > > > > + * When neither "summary" interrupt, nor "intx" interrupt was
> > > > > + * specified in DT then unmask all legacy INTx interrupts as in
> > > > > + * this case driver does not provide a way for masking and
> > > > > + * unmasking of individual legacy INTx interrupts. In this case
> > > > > + * all interrupts, including legacy INTx are reported via one
> > > > > + * shared GIC source and therefore kernel cannot distinguish
> > > > > + * which individual legacy INTx was triggered. These interrupts
> > > > > + * are shared, so it should not cause any issue. Just
> > > > > + * performance penalty as every PCIe interrupt handler needs to
> > > > > + * be called when some interrupt is triggered.
> > > > > + */
> > > > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > > > + unmask |= PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
> > > > > + PCIE_INT_INTX(2) | PCIE_INT_INTX(3);
> > > > > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > > >
> > > > Maybe worth printing a warning here, so that the user knows they are
> > > > on thin ice.
> > >
> > > Ok. I can add it here. Anyway, this is default current state without
> > > this patch.
> > >
> > > > > + }
> > > > > }
> > > > >
> > > > > static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
> > > > > @@ -924,6 +949,109 @@ static struct pci_ops mvebu_pcie_ops = {
> > > > > .write = mvebu_pcie_wr_conf,
> > > > > };
> > > > >
> > > > > +static void mvebu_pcie_intx_irq_mask(struct irq_data *d)
> > > > > +{
> > > > > + struct mvebu_pcie_port *port = d->domain->host_data;
> > > > > + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> > > > > + unsigned long flags;
> > > > > + u32 unmask;
> > > > > +
> > > > > + raw_spin_lock_irqsave(&port->irq_lock, flags);
> > > > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > > > + unmask &= ~PCIE_INT_INTX(hwirq);
> > > > > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > > > > + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> > > > > +}
> > > > > +
> > > > > +static void mvebu_pcie_intx_irq_unmask(struct irq_data *d)
> > > > > +{
> > > > > + struct mvebu_pcie_port *port = d->domain->host_data;
> > > > > + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> > > > > + unsigned long flags;
> > > > > + u32 unmask;
> > > > > +
> > > > > + raw_spin_lock_irqsave(&port->irq_lock, flags);
> > > > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > > > + unmask |= PCIE_INT_INTX(hwirq);
> > > > > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > > > > + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> > > > > +}
> > > > > +
> > > > > +static int mvebu_pcie_intx_irq_map(struct irq_domain *h,
> > > > > + unsigned int virq, irq_hw_number_t hwirq)
> > > > > +{
> > > > > + struct mvebu_pcie_port *port = h->host_data;
> > > > > +
> > > > > + irq_set_status_flags(virq, IRQ_LEVEL);
> > > > > + irq_set_chip_and_handler(virq, &port->intx_irq_chip, handle_level_irq);
> > > > > + irq_set_chip_data(virq, port);
> > > > > +
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static const struct irq_domain_ops mvebu_pcie_intx_irq_domain_ops = {
> > > > > + .map = mvebu_pcie_intx_irq_map,
> > > > > + .xlate = irq_domain_xlate_onecell,
> > > > > +};
> > > > > +
> > > > > +static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
> > > > > +{
> > > > > + struct device *dev = &port->pcie->pdev->dev;
> > > > > + struct device_node *pcie_intc_node;
> > > > > +
> > > > > + raw_spin_lock_init(&port->irq_lock);
> > > > > +
> > > > > + port->intx_irq_chip.name = devm_kasprintf(dev, GFP_KERNEL,
> > > > > + "mvebu-%s-INTx",
> > > > > + port->name);
> > > >
> > > > That's exactly what I really don't want to see. It prevents sharing of
> > > > the irq_chip structure, and gets in the way of making it const in the
> > > > future. Yes, I know that some drivers do that. I can't fix those,
> > > > because /proc/interrupts is ABI. But I really don't want to see more
> > > > of these.
> > >
> > > Well, I do not understand why it should be shared and with who. HW has N
> > > independent IRQ chips for legacy interrupts. And each one will be
> > > specified in DT per HW layout / design.
> >
> > If you have multiple ports, all the ports can share the irq_chip
> > structure. Actually scratch that. They *MUST* share the structure. The
> > only reason you're not sharing it is to be able to print this useless
> > string in /proc/interrupts.
>
> What is the point of sharing one irq chip if HW has N independent irq
> chips (for legacy interrupts)? I do not catch it yet. And I do not care
> here for /proc/interrupts, so also I have not caught what do you mean be
> last sentence with "the only reason".
>
> And I still do not see how it could even work to have just one irq chip
> and one irq domain as each irq domain needs to know to which port it
> belongs, so it can mask/unmask interrupts from correct port. Also
> initialization of domain is taking DT node and for each port it is
> different.
>
> So I'm somehow confused here...
>
> The improvement in this patch is to be able to mask INTA interrupts on
> port 1 and let INTA interrupts unmasked on port 2 if there drivers are
> interested only for interrupts from device connected to port 2.
>
> And if all interrupts are going to be shared (again) then it does not
> solve any problem.
You are completely missing my point. I'm talking about data
structures, you're talking about interrupts. You have this:
struct mvebu_pcie_port {
// Tons of stuff
struct irq_chip intx_chip;
};
What I want you to do is:
struct mvebu_pcie_port {
// Tons of stuff
};
static struct irq_chip intx_chip = {
.name = "INTx",
.irq_mask = mvebu_pcie_intx_irq_mask,
.irq_unmask = mvebu_pcie_intx_irq_unmask;
};
That's it. No more, no less.
M.
--
Without deviation from the norm, progress is not possible.
On Thu, 06 Jan 2022 16:27:44 +0000
Marc Zyngier <[email protected]> wrote:
> On Thu, 06 Jan 2022 16:20:47 +0000,
> Pali Rohár <[email protected]> wrote:
> >
> > On Thursday 06 January 2022 15:55:11 Marc Zyngier wrote:
> > > On Thu, 06 Jan 2022 15:44:47 +0000,
> > > Pali Rohár <[email protected]> wrote:
> > > >
> > > > On Thursday 06 January 2022 15:28:20 Marc Zyngier wrote:
> > > > > On Wed, 05 Jan 2022 15:02:38 +0000,
> > > > > Pali Rohár <[email protected]> wrote:
> > > > > >
> > > > > > This adds support for legacy INTx interrupts received from other PCIe
> > > > > > devices and which are reported by a new INTx irq chip.
> > > > > >
> > > > > > With this change, kernel can distinguish between INTA, INTB, INTC and INTD
> > > > > > interrupts.
> > > > > >
> > > > > > Note that for this support, device tree files has to be properly adjusted
> > > > > > to provide "interrupts" or "interrupts-extended" property with intx
> > > > > > interrupt source, "interrupt-names" property with "intx" string and also
> > > > > > 'interrupt-controller' subnode must be defined.
> > > > > >
> > > > > > If device tree files do not provide these nodes then driver would work as
> > > > > > before.
> > > > > >
> > > > > > Signed-off-by: Pali Rohár <[email protected]>
> > > > > > ---
> > > > > > drivers/pci/controller/pci-mvebu.c | 182 +++++++++++++++++++++++++++--
> > > > > > 1 file changed, 174 insertions(+), 8 deletions(-)
> > > > > >
> > > > > > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > > > > > index 1e90ab888075..04bcdd7b7a6d 100644
> > > > > > --- a/drivers/pci/controller/pci-mvebu.c
> > > > > > +++ b/drivers/pci/controller/pci-mvebu.c
> > > > > > @@ -54,9 +54,10 @@
> > > > > > PCIE_CONF_ADDR_EN)
> > > > > > #define PCIE_CONF_DATA_OFF 0x18fc
> > > > > > #define PCIE_INT_CAUSE_OFF 0x1900
> > > > > > +#define PCIE_INT_UNMASK_OFF 0x1910
> > > > > > +#define PCIE_INT_INTX(i) BIT(24+i)
> > > > > > #define PCIE_INT_PM_PME BIT(28)
> > > > > > -#define PCIE_MASK_OFF 0x1910
> > > > > > -#define PCIE_MASK_ENABLE_INTS 0x0f000000
> > > > > > +#define PCIE_INT_ALL_MASK GENMASK(31, 0)
> > > > > > #define PCIE_CTRL_OFF 0x1a00
> > > > > > #define PCIE_CTRL_X1_MODE 0x0001
> > > > > > #define PCIE_CTRL_RC_MODE BIT(1)
> > > > > > @@ -110,6 +111,10 @@ struct mvebu_pcie_port {
> > > > > > struct mvebu_pcie_window iowin;
> > > > > > u32 saved_pcie_stat;
> > > > > > struct resource regs;
> > > > > > + struct irq_domain *intx_irq_domain;
> > > > > > + struct irq_chip intx_irq_chip;
> > > > >
> > > > > Why is this structure per port? It really should be global. Printing
> > > > > the port number in the name isn't enough of a reason.
> > > >
> > > > Because each port has its own independent set of INTA-INTD
> > > > interrupts.
> > >
> > > That doesn't warrant a copy of an irq_chip structure that contains the
> > > exact same callbacks, and only differs by *a string*. And the use of
> > > this string is only to end-up in /proc/interrupts, which is totally
> > > pointless.
> > >
> > > >
> > > > > > + raw_spinlock_t irq_lock;
> > > > > > + int intx_irq;
> > > > > > };
> > > > > >
> > > > > > static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
> > > > > > @@ -235,7 +240,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
> > > > > >
> > > > > > static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > > > {
> > > > > > - u32 ctrl, lnkcap, cmd, dev_rev, mask;
> > > > > > + u32 ctrl, lnkcap, cmd, dev_rev, unmask;
> > > > > >
> > > > > > /* Setup PCIe controller to Root Complex mode. */
> > > > > > ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> > > > > > @@ -288,10 +293,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > > > /* Point PCIe unit MBUS decode windows to DRAM space. */
> > > > > > mvebu_pcie_setup_wins(port);
> > > > > >
> > > > > > - /* Enable interrupt lines A-D. */
> > > > > > - mask = mvebu_readl(port, PCIE_MASK_OFF);
> > > > > > - mask |= PCIE_MASK_ENABLE_INTS;
> > > > > > - mvebu_writel(port, mask, PCIE_MASK_OFF);
> > > > > > + /* Mask all interrupt sources. */
> > > > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > > > > > +
> > > > > > + /* Clear all interrupt causes. */
> > > > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > > > > > +
> > > > > > + if (port->intx_irq <= 0) {
> > > > > > + /*
> > > > > > + * When neither "summary" interrupt, nor "intx" interrupt was
> > > > > > + * specified in DT then unmask all legacy INTx interrupts as in
> > > > > > + * this case driver does not provide a way for masking and
> > > > > > + * unmasking of individual legacy INTx interrupts. In this case
> > > > > > + * all interrupts, including legacy INTx are reported via one
> > > > > > + * shared GIC source and therefore kernel cannot distinguish
> > > > > > + * which individual legacy INTx was triggered. These interrupts
> > > > > > + * are shared, so it should not cause any issue. Just
> > > > > > + * performance penalty as every PCIe interrupt handler needs to
> > > > > > + * be called when some interrupt is triggered.
> > > > > > + */
> > > > > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > > > > + unmask |= PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
> > > > > > + PCIE_INT_INTX(2) | PCIE_INT_INTX(3);
> > > > > > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > > > >
> > > > > Maybe worth printing a warning here, so that the user knows they are
> > > > > on thin ice.
> > > >
> > > > Ok. I can add it here. Anyway, this is default current state without
> > > > this patch.
> > > >
> > > > > > + }
> > > > > > }
> > > > > >
> > > > > > static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
> > > > > > @@ -924,6 +949,109 @@ static struct pci_ops mvebu_pcie_ops = {
> > > > > > .write = mvebu_pcie_wr_conf,
> > > > > > };
> > > > > >
> > > > > > +static void mvebu_pcie_intx_irq_mask(struct irq_data *d)
> > > > > > +{
> > > > > > + struct mvebu_pcie_port *port = d->domain->host_data;
> > > > > > + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> > > > > > + unsigned long flags;
> > > > > > + u32 unmask;
> > > > > > +
> > > > > > + raw_spin_lock_irqsave(&port->irq_lock, flags);
> > > > > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > > > > + unmask &= ~PCIE_INT_INTX(hwirq);
> > > > > > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > > > > > + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> > > > > > +}
> > > > > > +
> > > > > > +static void mvebu_pcie_intx_irq_unmask(struct irq_data *d)
> > > > > > +{
> > > > > > + struct mvebu_pcie_port *port = d->domain->host_data;
> > > > > > + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> > > > > > + unsigned long flags;
> > > > > > + u32 unmask;
> > > > > > +
> > > > > > + raw_spin_lock_irqsave(&port->irq_lock, flags);
> > > > > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > > > > + unmask |= PCIE_INT_INTX(hwirq);
> > > > > > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > > > > > + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> > > > > > +}
> > > > > > +
> > > > > > +static int mvebu_pcie_intx_irq_map(struct irq_domain *h,
> > > > > > + unsigned int virq, irq_hw_number_t hwirq)
> > > > > > +{
> > > > > > + struct mvebu_pcie_port *port = h->host_data;
> > > > > > +
> > > > > > + irq_set_status_flags(virq, IRQ_LEVEL);
> > > > > > + irq_set_chip_and_handler(virq, &port->intx_irq_chip, handle_level_irq);
> > > > > > + irq_set_chip_data(virq, port);
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static const struct irq_domain_ops mvebu_pcie_intx_irq_domain_ops = {
> > > > > > + .map = mvebu_pcie_intx_irq_map,
> > > > > > + .xlate = irq_domain_xlate_onecell,
> > > > > > +};
> > > > > > +
> > > > > > +static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
> > > > > > +{
> > > > > > + struct device *dev = &port->pcie->pdev->dev;
> > > > > > + struct device_node *pcie_intc_node;
> > > > > > +
> > > > > > + raw_spin_lock_init(&port->irq_lock);
> > > > > > +
> > > > > > + port->intx_irq_chip.name = devm_kasprintf(dev, GFP_KERNEL,
> > > > > > + "mvebu-%s-INTx",
> > > > > > + port->name);
> > > > >
> > > > > That's exactly what I really don't want to see. It prevents sharing of
> > > > > the irq_chip structure, and gets in the way of making it const in the
> > > > > future. Yes, I know that some drivers do that. I can't fix those,
> > > > > because /proc/interrupts is ABI. But I really don't want to see more
> > > > > of these.
> > > >
> > > > Well, I do not understand why it should be shared and with who. HW has N
> > > > independent IRQ chips for legacy interrupts. And each one will be
> > > > specified in DT per HW layout / design.
> > >
> > > If you have multiple ports, all the ports can share the irq_chip
> > > structure. Actually scratch that. They *MUST* share the structure. The
> > > only reason you're not sharing it is to be able to print this useless
> > > string in /proc/interrupts.
> >
> > What is the point of sharing one irq chip if HW has N independent irq
> > chips (for legacy interrupts)? I do not catch it yet. And I do not care
> > here for /proc/interrupts, so also I have not caught what do you mean be
> > last sentence with "the only reason".
> >
> > And I still do not see how it could even work to have just one irq chip
> > and one irq domain as each irq domain needs to know to which port it
> > belongs, so it can mask/unmask interrupts from correct port. Also
> > initialization of domain is taking DT node and for each port it is
> > different.
> >
> > So I'm somehow confused here...
> >
> > The improvement in this patch is to be able to mask INTA interrupts on
> > port 1 and let INTA interrupts unmasked on port 2 if there drivers are
> > interested only for interrupts from device connected to port 2.
> >
> > And if all interrupts are going to be shared (again) then it does not
> > solve any problem.
>
> You are completely missing my point. I'm talking about data
> structures, you're talking about interrupts. You have this:
>
> struct mvebu_pcie_port {
> // Tons of stuff
> struct irq_chip intx_chip;
> };
>
> What I want you to do is:
>
> struct mvebu_pcie_port {
> // Tons of stuff
> };
>
> static struct irq_chip intx_chip = {
> .name = "INTx",
> .irq_mask = mvebu_pcie_intx_irq_mask,
> .irq_unmask = mvebu_pcie_intx_irq_unmask;
> };
>
> That's it. No more, no less.
>
> M.
>
Hmm, but struct irq_chip contains a dynamic member,
struct device *parent_device;
Isn't that used? Or are you planning to kill it?
Marek
On Thu, 06 Jan 2022 17:20:44 +0000,
Marek Behún <[email protected]> wrote:
>
> On Thu, 06 Jan 2022 16:27:44 +0000
> Marc Zyngier <[email protected]> wrote:
> > You are completely missing my point. I'm talking about data
> > structures, you're talking about interrupts. You have this:
> >
> > struct mvebu_pcie_port {
> > // Tons of stuff
> > struct irq_chip intx_chip;
> > };
> >
> > What I want you to do is:
> >
> > struct mvebu_pcie_port {
> > // Tons of stuff
> > };
> >
> > static struct irq_chip intx_chip = {
> > .name = "INTx",
> > .irq_mask = mvebu_pcie_intx_irq_mask,
> > .irq_unmask = mvebu_pcie_intx_irq_unmask;
> > };
> >
> > That's it. No more, no less.
> >
> > M.
> >
>
> Hmm, but struct irq_chip contains a dynamic member,
> struct device *parent_device;
> Isn't that used? Or are you planning to kill it?
Indeed, and I am definitely planning to kill it. This is the wrong
place for this stuff, and I want it gone. There are thankfully very
few users of this misfeature.
M.
--
Without deviation from the norm, progress is not possible.
On Thursday 06 January 2022 17:31:36 Marc Zyngier wrote:
> On Thu, 06 Jan 2022 17:20:44 +0000,
> Marek Behún <[email protected]> wrote:
> >
> > On Thu, 06 Jan 2022 16:27:44 +0000
> > Marc Zyngier <[email protected]> wrote:
> > > You are completely missing my point. I'm talking about data
> > > structures, you're talking about interrupts. You have this:
> > >
> > > struct mvebu_pcie_port {
> > > // Tons of stuff
> > > struct irq_chip intx_chip;
> > > };
> > >
> > > What I want you to do is:
> > >
> > > struct mvebu_pcie_port {
> > > // Tons of stuff
> > > };
> > >
> > > static struct irq_chip intx_chip = {
> > > .name = "INTx",
> > > .irq_mask = mvebu_pcie_intx_irq_mask,
> > > .irq_unmask = mvebu_pcie_intx_irq_unmask;
> > > };
> > >
> > > That's it. No more, no less.
> > >
> > > M.
> > >
> >
> > Hmm, but struct irq_chip contains a dynamic member,
> > struct device *parent_device;
> > Isn't that used? Or are you planning to kill it?
>
> Indeed, and I am definitely planning to kill it. This is the wrong
> place for this stuff, and I want it gone. There are thankfully very
> few users of this misfeature.
Ok, so what about this change?
diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
index 1e90ab888075..5c816338a569 100644
--- a/drivers/pci/controller/pci-mvebu.c
+++ b/drivers/pci/controller/pci-mvebu.c
@@ -54,9 +54,10 @@
PCIE_CONF_ADDR_EN)
#define PCIE_CONF_DATA_OFF 0x18fc
#define PCIE_INT_CAUSE_OFF 0x1900
+#define PCIE_INT_UNMASK_OFF 0x1910
+#define PCIE_INT_INTX(i) BIT(24+i)
#define PCIE_INT_PM_PME BIT(28)
-#define PCIE_MASK_OFF 0x1910
-#define PCIE_MASK_ENABLE_INTS 0x0f000000
+#define PCIE_INT_ALL_MASK GENMASK(31, 0)
#define PCIE_CTRL_OFF 0x1a00
#define PCIE_CTRL_X1_MODE 0x0001
#define PCIE_CTRL_RC_MODE BIT(1)
@@ -110,6 +111,9 @@ struct mvebu_pcie_port {
struct mvebu_pcie_window iowin;
u32 saved_pcie_stat;
struct resource regs;
+ struct irq_domain *intx_irq_domain;
+ raw_spinlock_t irq_lock;
+ int intx_irq;
};
static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
@@ -235,7 +239,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
{
- u32 ctrl, lnkcap, cmd, dev_rev, mask;
+ u32 ctrl, lnkcap, cmd, dev_rev, unmask;
/* Setup PCIe controller to Root Complex mode. */
ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
@@ -288,10 +292,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
/* Point PCIe unit MBUS decode windows to DRAM space. */
mvebu_pcie_setup_wins(port);
- /* Enable interrupt lines A-D. */
- mask = mvebu_readl(port, PCIE_MASK_OFF);
- mask |= PCIE_MASK_ENABLE_INTS;
- mvebu_writel(port, mask, PCIE_MASK_OFF);
+ /* Mask all interrupt sources. */
+ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
+
+ /* Clear all interrupt causes. */
+ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
+
+ if (port->intx_irq <= 0) {
+ /*
+ * When neither "summary" interrupt, nor "intx" interrupt was
+ * specified in DT then unmask all legacy INTx interrupts as in
+ * this case driver does not provide a way for masking and
+ * unmasking of individual legacy INTx interrupts. In this case
+ * all interrupts, including legacy INTx are reported via one
+ * shared GIC source and therefore kernel cannot distinguish
+ * which individual legacy INTx was triggered. These interrupts
+ * are shared, so it should not cause any issue. Just
+ * performance penalty as every PCIe interrupt handler needs to
+ * be called when some interrupt is triggered.
+ */
+ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+ unmask |= PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
+ PCIE_INT_INTX(2) | PCIE_INT_INTX(3);
+ mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
+ }
}
static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
@@ -924,6 +948,108 @@ static struct pci_ops mvebu_pcie_ops = {
.write = mvebu_pcie_wr_conf,
};
+static void mvebu_pcie_intx_irq_mask(struct irq_data *d)
+{
+ struct mvebu_pcie_port *port = d->domain->host_data;
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+ unsigned long flags;
+ u32 unmask;
+
+ raw_spin_lock_irqsave(&port->irq_lock, flags);
+ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+ unmask &= ~PCIE_INT_INTX(hwirq);
+ mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
+ raw_spin_unlock_irqrestore(&port->irq_lock, flags);
+}
+
+static void mvebu_pcie_intx_irq_unmask(struct irq_data *d)
+{
+ struct mvebu_pcie_port *port = d->domain->host_data;
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+ unsigned long flags;
+ u32 unmask;
+
+ raw_spin_lock_irqsave(&port->irq_lock, flags);
+ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+ unmask |= PCIE_INT_INTX(hwirq);
+ mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
+ raw_spin_unlock_irqrestore(&port->irq_lock, flags);
+}
+
+static struct irq_chip intx_irq_chip = {
+ .name = "mvebu-INTx",
+ .irq_mask = mvebu_pcie_intx_irq_mask,
+ .irq_unmask = mvebu_pcie_intx_irq_unmask,
+};
+
+static int mvebu_pcie_intx_irq_map(struct irq_domain *h,
+ unsigned int virq, irq_hw_number_t hwirq)
+{
+ struct mvebu_pcie_port *port = h->host_data;
+
+ irq_set_status_flags(virq, IRQ_LEVEL);
+ irq_set_chip_and_handler(virq, &intx_irq_chip, handle_level_irq);
+ irq_set_chip_data(virq, port);
+
+ return 0;
+}
+
+static const struct irq_domain_ops mvebu_pcie_intx_irq_domain_ops = {
+ .map = mvebu_pcie_intx_irq_map,
+ .xlate = irq_domain_xlate_onecell,
+};
+
+static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
+{
+ struct device *dev = &port->pcie->pdev->dev;
+ struct device_node *pcie_intc_node;
+
+ raw_spin_lock_init(&port->irq_lock);
+
+ pcie_intc_node = of_get_next_child(port->dn, NULL);
+ if (!pcie_intc_node) {
+ dev_err(dev, "No PCIe Intc node found for %s\n", port->name);
+ return -ENODEV;
+ }
+
+ port->intx_irq_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX,
+ &mvebu_pcie_intx_irq_domain_ops,
+ port);
+ of_node_put(pcie_intc_node);
+ if (!port->intx_irq_domain) {
+ dev_err(dev, "Failed to get INTx IRQ domain for %s\n", port->name);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void mvebu_pcie_irq_handler(struct irq_desc *desc)
+{
+ struct mvebu_pcie_port *port = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ struct device *dev = &port->pcie->pdev->dev;
+ u32 cause, unmask, status;
+ int i;
+
+ chained_irq_enter(chip, desc);
+
+ cause = mvebu_readl(port, PCIE_INT_CAUSE_OFF);
+ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+ status = cause & unmask;
+
+ /* Process legacy INTx interrupts */
+ for (i = 0; i < PCI_NUM_INTX; i++) {
+ if (!(status & PCIE_INT_INTX(i)))
+ continue;
+
+ if (generic_handle_domain_irq(port->intx_irq_domain, i) == -EINVAL)
+ dev_err_ratelimited(dev, "unexpected INT%c IRQ\n", (char)i+'A');
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
static int mvebu_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
{
/* Interrupt support on mvebu emulated bridges is not implemented yet */
@@ -1121,6 +1247,21 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
port->io_attr = -1;
}
+ /*
+ * Old DT bindings do not contain "intx" interrupt
+ * so do not fail probing driver when interrupt does not exist.
+ */
+ port->intx_irq = of_irq_get_byname(child, "intx");
+ if (port->intx_irq == -EPROBE_DEFER) {
+ ret = port->intx_irq;
+ goto err;
+ }
+ if (port->intx_irq <= 0) {
+ dev_warn(dev, "%s: legacy INTx interrupts cannot be masked individually, "
+ "%pOF does not contain intx interrupt\n",
+ port->name, child);
+ }
+
reset_gpio = of_get_named_gpio_flags(child, "reset-gpios", 0, &flags);
if (reset_gpio == -EPROBE_DEFER) {
ret = reset_gpio;
@@ -1317,6 +1458,7 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
for (i = 0; i < pcie->nports; i++) {
struct mvebu_pcie_port *port = &pcie->ports[i];
+ int irq = port->intx_irq;
child = port->dn;
if (!child)
@@ -1344,6 +1486,22 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
continue;
}
+ if (irq > 0) {
+ ret = mvebu_pcie_init_irq_domain(port);
+ if (ret) {
+ dev_err(dev, "%s: cannot init irq domain\n",
+ port->name);
+ pci_bridge_emul_cleanup(&port->bridge);
+ devm_iounmap(dev, port->base);
+ port->base = NULL;
+ mvebu_pcie_powerdown(port);
+ continue;
+ }
+ irq_set_chained_handler_and_data(irq,
+ mvebu_pcie_irq_handler,
+ port);
+ }
+
/*
* PCIe topology exported by mvebu hw is quite complicated. In
* reality has something like N fully independent host bridges
@@ -1448,6 +1606,7 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
for (i = 0; i < pcie->nports; i++) {
struct mvebu_pcie_port *port = &pcie->ports[i];
+ int irq = port->intx_irq;
if (!port->base)
continue;
@@ -1458,7 +1617,17 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
mvebu_writel(port, cmd, PCIE_CMD_OFF);
/* Mask all interrupt sources. */
- mvebu_writel(port, 0, PCIE_MASK_OFF);
+ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
+
+ /* Clear all interrupt causes. */
+ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
+
+ /* Remove IRQ domains. */
+ if (port->intx_irq_domain)
+ irq_domain_remove(port->intx_irq_domain);
+
+ if (irq > 0)
+ irq_set_chained_handler_and_data(irq, NULL, NULL);
/* Free config space for emulated root bridge. */
pci_bridge_emul_cleanup(&port->bridge);
--
2.20.1
On Fri, 07 Jan 2022 11:50:53 +0000,
Pali Rohár <[email protected]> wrote:
>
> On Thursday 06 January 2022 17:31:36 Marc Zyngier wrote:
> > On Thu, 06 Jan 2022 17:20:44 +0000,
> > Marek Behún <[email protected]> wrote:
> > >
> > > On Thu, 06 Jan 2022 16:27:44 +0000
> > > Marc Zyngier <[email protected]> wrote:
> > > > You are completely missing my point. I'm talking about data
> > > > structures, you're talking about interrupts. You have this:
> > > >
> > > > struct mvebu_pcie_port {
> > > > // Tons of stuff
> > > > struct irq_chip intx_chip;
> > > > };
> > > >
> > > > What I want you to do is:
> > > >
> > > > struct mvebu_pcie_port {
> > > > // Tons of stuff
> > > > };
> > > >
> > > > static struct irq_chip intx_chip = {
> > > > .name = "INTx",
> > > > .irq_mask = mvebu_pcie_intx_irq_mask,
> > > > .irq_unmask = mvebu_pcie_intx_irq_unmask;
> > > > };
> > > >
> > > > That's it. No more, no less.
> > > >
> > > > M.
> > > >
> > >
> > > Hmm, but struct irq_chip contains a dynamic member,
> > > struct device *parent_device;
> > > Isn't that used? Or are you planning to kill it?
> >
> > Indeed, and I am definitely planning to kill it. This is the wrong
> > place for this stuff, and I want it gone. There are thankfully very
> > few users of this misfeature.
>
> Ok, so what about this change?
>
> @@ -1458,7 +1617,17 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
> mvebu_writel(port, cmd, PCIE_CMD_OFF);
>
> /* Mask all interrupt sources. */
> - mvebu_writel(port, 0, PCIE_MASK_OFF);
> + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> +
> + /* Clear all interrupt causes. */
> + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> +
> + /* Remove IRQ domains. */
> + if (port->intx_irq_domain)
> + irq_domain_remove(port->intx_irq_domain);
> +
> + if (irq > 0)
> + irq_set_chained_handler_and_data(irq, NULL, NULL);
You really want this to be done *before* you remove the domain, as
there still could be interrupts in flight at this point.
M.
--
Without deviation from the norm, progress is not possible.
On Wed, 05 Jan 2022 16:02:37 +0100, Pali Roh?r wrote:
> Signed-off-by: Pali Roh?r <[email protected]>
> ---
> Documentation/devicetree/bindings/pci/mvebu-pci.txt | 5 +++++
> 1 file changed, 5 insertions(+)
>
Acked-by: Rob Herring <[email protected]>
On Wednesday 05 January 2022 09:41:51 Rob Herring wrote:
> On Wed, Jan 5, 2022 at 9:03 AM Pali Rohár <[email protected]> wrote:
> >
> > Split struct pci_ops between ops and child_ops. Member ops is used for
> > accessing PCIe Root Ports via pci-bridge-emul.c driver and child_ops for
> > accessing real PCIe cards.
> >
> > There is no need to mix these two struct pci_ops into one as PCI core code
> > already provides separate callbacks via bridge->ops and bridge->child_ops.
>
> I had similar patches including mvebu that I never got around to
> sending out. I pushed the branch out now at least[1].
Are you going to finish your patch series and send it? Because if yes,
I can drop this my patch in v3 and let all ->child_ops conversion for
you.
On Tue, Jan 11, 2022 at 7:44 PM Pali Rohár <[email protected]> wrote:
>
> On Wednesday 05 January 2022 09:41:51 Rob Herring wrote:
> > On Wed, Jan 5, 2022 at 9:03 AM Pali Rohár <[email protected]> wrote:
> > >
> > > Split struct pci_ops between ops and child_ops. Member ops is used for
> > > accessing PCIe Root Ports via pci-bridge-emul.c driver and child_ops for
> > > accessing real PCIe cards.
> > >
> > > There is no need to mix these two struct pci_ops into one as PCI core code
> > > already provides separate callbacks via bridge->ops and bridge->child_ops.
> >
> > I had similar patches including mvebu that I never got around to
> > sending out. I pushed the branch out now at least[1].
>
> Are you going to finish your patch series and send it? Because if yes,
> I can drop this my patch in v3 and let all ->child_ops conversion for
> you.
Not any time soon.
Rob
This patch series extends pci-bridge-emul.c driver to emulate PCI Subsystem
Vendor ID capability and PCIe extended capabilities. And then implement
in pci-mvebu.c driver support for PCI Subsystem Vendor IDs, PCIe AER
registers, support for legacy INTx interrupts, configuration for X1/X4
mode and usage of new PCI child_ops API.
This patch series depends on other pci-mvebu and pci-bridge-emul patches from:
https://lore.kernel.org/linux-pci/[email protected]/
Changes in v2:
* use static structures for INTx interrupts
* remove INTx domain after unregistering INTx handler
Pali Rohár (9):
PCI: pci-bridge-emul: Add support for PCI Bridge Subsystem Vendor ID
capability
dt-bindings: PCI: mvebu: Add num-lanes property
PCI: mvebu: Correctly configure x1/x4 mode
PCI: mvebu: Add support for PCI Bridge Subsystem Vendor ID on emulated
bridge
PCI: mvebu: Add support for Advanced Error Reporting registers on
emulated bridge
PCI: mvebu: Use child_ops API
dt-bindings: PCI: mvebu: Update information about intx interrupts
PCI: mvebu: Implement support for legacy INTx interrupts
ARM: dts: armada-385.dtsi: Add definitions for PCIe legacy INTx
interrupts
Russell King (2):
PCI: pci-bridge-emul: Re-arrange register tests
PCI: pci-bridge-emul: Add support for PCIe extended capabilities
.../devicetree/bindings/pci/mvebu-pci.txt | 16 +
arch/arm/boot/dts/armada-385.dtsi | 52 ++-
drivers/pci/controller/pci-mvebu.c | 355 +++++++++++++++---
drivers/pci/pci-bridge-emul.c | 167 +++++---
drivers/pci/pci-bridge-emul.h | 17 +
5 files changed, 497 insertions(+), 110 deletions(-)
--
2.20.1
Controller driver needs to correctly configure PCIe link if it contains 1
or 4 SerDes PCIe lanes. Therefore add a new 'num-lanes' DT property for
mvebu PCIe controller. Property 'num-lanes' seems to be de-facto standard
way how number of lanes is specified in other PCIe controllers.
Signed-off-by: Pali Rohár <[email protected]>
Acked-by: Rob Herring <[email protected]>
---
Documentation/devicetree/bindings/pci/mvebu-pci.txt | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/Documentation/devicetree/bindings/pci/mvebu-pci.txt b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
index 6173af6885f8..24225852bce0 100644
--- a/Documentation/devicetree/bindings/pci/mvebu-pci.txt
+++ b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
@@ -77,6 +77,7 @@ and the following optional properties:
- marvell,pcie-lane: the physical PCIe lane number, for ports having
multiple lanes. If this property is not found, we assume that the
value is 0.
+- num-lanes: number of SerDes PCIe lanes for this link (1 or 4)
- reset-gpios: optional GPIO to PERST#
- reset-delay-us: delay in us to wait after reset de-assertion, if not
specified will default to 100ms, as required by the PCIe specification.
@@ -141,6 +142,7 @@ pcie-controller {
interrupt-map = <0 0 0 0 &mpic 58>;
marvell,pcie-port = <0>;
marvell,pcie-lane = <0>;
+ num-lanes = <1>;
/* low-active PERST# reset on GPIO 25 */
reset-gpios = <&gpio0 25 1>;
/* wait 20ms for device settle after reset deassertion */
@@ -161,6 +163,7 @@ pcie-controller {
interrupt-map = <0 0 0 0 &mpic 59>;
marvell,pcie-port = <0>;
marvell,pcie-lane = <1>;
+ num-lanes = <1>;
clocks = <&gateclk 6>;
};
@@ -177,6 +180,7 @@ pcie-controller {
interrupt-map = <0 0 0 0 &mpic 60>;
marvell,pcie-port = <0>;
marvell,pcie-lane = <2>;
+ num-lanes = <1>;
clocks = <&gateclk 7>;
};
@@ -193,6 +197,7 @@ pcie-controller {
interrupt-map = <0 0 0 0 &mpic 61>;
marvell,pcie-port = <0>;
marvell,pcie-lane = <3>;
+ num-lanes = <1>;
clocks = <&gateclk 8>;
};
@@ -209,6 +214,7 @@ pcie-controller {
interrupt-map = <0 0 0 0 &mpic 62>;
marvell,pcie-port = <1>;
marvell,pcie-lane = <0>;
+ num-lanes = <1>;
clocks = <&gateclk 9>;
};
@@ -225,6 +231,7 @@ pcie-controller {
interrupt-map = <0 0 0 0 &mpic 63>;
marvell,pcie-port = <1>;
marvell,pcie-lane = <1>;
+ num-lanes = <1>;
clocks = <&gateclk 10>;
};
@@ -241,6 +248,7 @@ pcie-controller {
interrupt-map = <0 0 0 0 &mpic 64>;
marvell,pcie-port = <1>;
marvell,pcie-lane = <2>;
+ num-lanes = <1>;
clocks = <&gateclk 11>;
};
@@ -257,6 +265,7 @@ pcie-controller {
interrupt-map = <0 0 0 0 &mpic 65>;
marvell,pcie-port = <1>;
marvell,pcie-lane = <3>;
+ num-lanes = <1>;
clocks = <&gateclk 12>;
};
@@ -273,6 +282,7 @@ pcie-controller {
interrupt-map = <0 0 0 0 &mpic 99>;
marvell,pcie-port = <2>;
marvell,pcie-lane = <0>;
+ num-lanes = <1>;
clocks = <&gateclk 26>;
};
@@ -289,6 +299,7 @@ pcie-controller {
interrupt-map = <0 0 0 0 &mpic 103>;
marvell,pcie-port = <3>;
marvell,pcie-lane = <0>;
+ num-lanes = <1>;
clocks = <&gateclk 27>;
};
};
--
2.20.1
This is read-only capability in PCI config space. Put it between base PCI
capability and base PCI Express capability.
Driver just have to specify subsystem_vendor_id and subsystem_id fields in
emulated bridge structure and pci-bridge-emul takes care of correctly
compose PCI Bridge Subsystem Vendor ID capability.
Signed-off-by: Pali Rohár <[email protected]>
---
drivers/pci/pci-bridge-emul.c | 69 +++++++++++++++++++++++++----------
drivers/pci/pci-bridge-emul.h | 2 +
2 files changed, 51 insertions(+), 20 deletions(-)
diff --git a/drivers/pci/pci-bridge-emul.c b/drivers/pci/pci-bridge-emul.c
index 9f4f173f0650..c84f423a5893 100644
--- a/drivers/pci/pci-bridge-emul.c
+++ b/drivers/pci/pci-bridge-emul.c
@@ -21,8 +21,11 @@
#include "pci-bridge-emul.h"
#define PCI_BRIDGE_CONF_END PCI_STD_HEADER_SIZEOF
+#define PCI_CAP_SSID_SIZEOF (PCI_SSVID_DEVICE_ID + 2)
+#define PCI_CAP_SSID_START PCI_BRIDGE_CONF_END
+#define PCI_CAP_SSID_END (PCI_CAP_SSID_START + PCI_CAP_SSID_SIZEOF)
#define PCI_CAP_PCIE_SIZEOF (PCI_EXP_SLTSTA2 + 2)
-#define PCI_CAP_PCIE_START PCI_BRIDGE_CONF_END
+#define PCI_CAP_PCIE_START PCI_CAP_SSID_END
#define PCI_CAP_PCIE_END (PCI_CAP_PCIE_START + PCI_CAP_PCIE_SIZEOF)
/**
@@ -315,6 +318,25 @@ struct pci_bridge_reg_behavior pcie_cap_regs_behavior[PCI_CAP_PCIE_SIZEOF / 4] =
},
};
+static pci_bridge_emul_read_status_t
+pci_bridge_emul_read_ssid(struct pci_bridge_emul *bridge, int reg, u32 *value)
+{
+ switch (reg) {
+ case PCI_CAP_LIST_ID:
+ *value = PCI_CAP_ID_SSVID |
+ (bridge->has_pcie ? (PCI_CAP_PCIE_START << 8) : 0);
+ return PCI_BRIDGE_EMUL_HANDLED;
+
+ case PCI_SSVID_VENDOR_ID:
+ *value = bridge->subsystem_vendor_id |
+ (bridge->subsystem_id << 16);
+ return PCI_BRIDGE_EMUL_HANDLED;
+
+ default:
+ return PCI_BRIDGE_EMUL_NOT_HANDLED;
+ }
+}
+
/*
* Initialize a pci_bridge_emul structure to represent a fake PCI
* bridge configuration space. The caller needs to have initialized
@@ -337,9 +359,17 @@ int pci_bridge_emul_init(struct pci_bridge_emul *bridge,
if (!bridge->pci_regs_behavior)
return -ENOMEM;
- if (bridge->has_pcie) {
+ if (bridge->subsystem_vendor_id)
+ bridge->conf.capabilities_pointer = PCI_CAP_SSID_START;
+ else if (bridge->has_pcie)
bridge->conf.capabilities_pointer = PCI_CAP_PCIE_START;
+ else
+ bridge->conf.capabilities_pointer = 0;
+
+ if (bridge->conf.capabilities_pointer)
bridge->conf.status |= cpu_to_le16(PCI_STATUS_CAP_LIST);
+
+ if (bridge->has_pcie) {
bridge->pcie_conf.cap_id = PCI_CAP_ID_EXP;
bridge->pcie_conf.cap |= cpu_to_le16(PCI_EXP_TYPE_ROOT_PORT << 4);
bridge->pcie_cap_regs_behavior =
@@ -423,26 +453,28 @@ int pci_bridge_emul_conf_read(struct pci_bridge_emul *bridge, int where,
read_op = bridge->ops->read_base;
cfgspace = (__le32 *) &bridge->conf;
behavior = bridge->pci_regs_behavior;
- } else if (!bridge->has_pcie) {
- /* PCIe space is not implemented, and no PCI capabilities */
- *value = 0;
- return PCIBIOS_SUCCESSFUL;
- } else if (reg < PCI_CAP_PCIE_END) {
+ } else if (reg >= PCI_CAP_SSID_START && reg < PCI_CAP_SSID_END && bridge->subsystem_vendor_id) {
+ /* Emulated PCI Bridge Subsystem Vendor ID capability */
+ reg -= PCI_CAP_SSID_START;
+ read_op = pci_bridge_emul_read_ssid;
+ cfgspace = NULL;
+ behavior = NULL;
+ } else if (reg >= PCI_CAP_PCIE_START && reg < PCI_CAP_PCIE_END && bridge->has_pcie) {
/* Our emulated PCIe capability */
reg -= PCI_CAP_PCIE_START;
read_op = bridge->ops->read_pcie;
cfgspace = (__le32 *) &bridge->pcie_conf;
behavior = bridge->pcie_cap_regs_behavior;
- } else if (reg < PCI_CFG_SPACE_SIZE) {
- /* Rest of PCI space not implemented */
- *value = 0;
- return PCIBIOS_SUCCESSFUL;
- } else {
+ } else if (reg >= PCI_CFG_SPACE_SIZE && bridge->has_pcie) {
/* PCIe extended capability space */
reg -= PCI_CFG_SPACE_SIZE;
read_op = bridge->ops->read_ext;
cfgspace = NULL;
behavior = NULL;
+ } else {
+ /* Not implemented */
+ *value = 0;
+ return PCIBIOS_SUCCESSFUL;
}
if (read_op)
@@ -500,24 +532,21 @@ int pci_bridge_emul_conf_write(struct pci_bridge_emul *bridge, int where,
write_op = bridge->ops->write_base;
cfgspace = (__le32 *) &bridge->conf;
behavior = bridge->pci_regs_behavior;
- } else if (!bridge->has_pcie) {
- /* PCIe space is not implemented, and no PCI capabilities */
- return PCIBIOS_SUCCESSFUL;
- } else if (reg < PCI_CAP_PCIE_END) {
+ } else if (reg >= PCI_CAP_PCIE_START && reg < PCI_CAP_PCIE_END && bridge->has_pcie) {
/* Our emulated PCIe capability */
reg -= PCI_CAP_PCIE_START;
write_op = bridge->ops->write_pcie;
cfgspace = (__le32 *) &bridge->pcie_conf;
behavior = bridge->pcie_cap_regs_behavior;
- } else if (reg < PCI_CFG_SPACE_SIZE) {
- /* Rest of PCI space not implemented */
- return PCIBIOS_SUCCESSFUL;
- } else {
+ } else if (reg >= PCI_CFG_SPACE_SIZE && bridge->has_pcie) {
/* PCIe extended capability space */
reg -= PCI_CFG_SPACE_SIZE;
write_op = bridge->ops->write_ext;
cfgspace = NULL;
behavior = NULL;
+ } else {
+ /* Not implemented */
+ return PCIBIOS_SUCCESSFUL;
}
shift = (where & 0x3) * 8;
diff --git a/drivers/pci/pci-bridge-emul.h b/drivers/pci/pci-bridge-emul.h
index 6b5f75b2ad02..71392b67471d 100644
--- a/drivers/pci/pci-bridge-emul.h
+++ b/drivers/pci/pci-bridge-emul.h
@@ -132,6 +132,8 @@ struct pci_bridge_emul {
struct pci_bridge_reg_behavior *pcie_cap_regs_behavior;
void *data;
bool has_pcie;
+ u16 subsystem_vendor_id;
+ u16 subsystem_id;
};
enum {
--
2.20.1
From: Russell King <[email protected]>
Re-arrange the tests for which sets of registers are being accessed so that
it is easier to add further regions later. No functional change.
Signed-off-by: Russell King <[email protected]>
[pali: Fix reading old value in pci_bridge_emul_conf_write]
Signed-off-by: Pali Rohár <[email protected]>
---
drivers/pci/pci-bridge-emul.c | 61 ++++++++++++++++++-----------------
1 file changed, 31 insertions(+), 30 deletions(-)
diff --git a/drivers/pci/pci-bridge-emul.c b/drivers/pci/pci-bridge-emul.c
index 033bbeb99176..56b2cb741498 100644
--- a/drivers/pci/pci-bridge-emul.c
+++ b/drivers/pci/pci-bridge-emul.c
@@ -418,25 +418,25 @@ int pci_bridge_emul_conf_read(struct pci_bridge_emul *bridge, int where,
__le32 *cfgspace;
const struct pci_bridge_reg_behavior *behavior;
- if (bridge->has_pcie && reg >= PCI_CAP_PCIE_END) {
- *value = 0;
- return PCIBIOS_SUCCESSFUL;
- }
-
- if (!bridge->has_pcie && reg >= PCI_BRIDGE_CONF_END) {
+ if (reg < PCI_BRIDGE_CONF_END) {
+ /* Emulated PCI space */
+ read_op = bridge->ops->read_base;
+ cfgspace = (__le32 *) &bridge->conf;
+ behavior = bridge->pci_regs_behavior;
+ } else if (!bridge->has_pcie) {
+ /* PCIe space is not implemented, and no PCI capabilities */
*value = 0;
return PCIBIOS_SUCCESSFUL;
- }
-
- if (bridge->has_pcie && reg >= PCI_CAP_PCIE_START) {
+ } else if (reg < PCI_CAP_PCIE_END) {
+ /* Our emulated PCIe capability */
reg -= PCI_CAP_PCIE_START;
read_op = bridge->ops->read_pcie;
cfgspace = (__le32 *) &bridge->pcie_conf;
behavior = bridge->pcie_cap_regs_behavior;
} else {
- read_op = bridge->ops->read_base;
- cfgspace = (__le32 *) &bridge->conf;
- behavior = bridge->pci_regs_behavior;
+ /* Beyond our PCIe space */
+ *value = 0;
+ return PCIBIOS_SUCCESSFUL;
}
if (read_op)
@@ -480,11 +480,27 @@ int pci_bridge_emul_conf_write(struct pci_bridge_emul *bridge, int where,
__le32 *cfgspace;
const struct pci_bridge_reg_behavior *behavior;
- if (bridge->has_pcie && reg >= PCI_CAP_PCIE_END)
- return PCIBIOS_SUCCESSFUL;
+ ret = pci_bridge_emul_conf_read(bridge, reg, 4, &old);
+ if (ret != PCIBIOS_SUCCESSFUL)
+ return ret;
- if (!bridge->has_pcie && reg >= PCI_BRIDGE_CONF_END)
+ if (reg < PCI_BRIDGE_CONF_END) {
+ /* Emulated PCI space */
+ write_op = bridge->ops->write_base;
+ cfgspace = (__le32 *) &bridge->conf;
+ behavior = bridge->pci_regs_behavior;
+ } else if (!bridge->has_pcie) {
+ /* PCIe space is not implemented, and no PCI capabilities */
return PCIBIOS_SUCCESSFUL;
+ } else if (reg < PCI_CAP_PCIE_END) {
+ /* Our emulated PCIe capability */
+ reg -= PCI_CAP_PCIE_START;
+ write_op = bridge->ops->write_pcie;
+ cfgspace = (__le32 *) &bridge->pcie_conf;
+ behavior = bridge->pcie_cap_regs_behavior;
+ } else {
+ return PCIBIOS_SUCCESSFUL;
+ }
shift = (where & 0x3) * 8;
@@ -497,21 +513,6 @@ int pci_bridge_emul_conf_write(struct pci_bridge_emul *bridge, int where,
else
return PCIBIOS_BAD_REGISTER_NUMBER;
- ret = pci_bridge_emul_conf_read(bridge, reg, 4, &old);
- if (ret != PCIBIOS_SUCCESSFUL)
- return ret;
-
- if (bridge->has_pcie && reg >= PCI_CAP_PCIE_START) {
- reg -= PCI_CAP_PCIE_START;
- write_op = bridge->ops->write_pcie;
- cfgspace = (__le32 *) &bridge->pcie_conf;
- behavior = bridge->pcie_cap_regs_behavior;
- } else {
- write_op = bridge->ops->write_base;
- cfgspace = (__le32 *) &bridge->conf;
- behavior = bridge->pci_regs_behavior;
- }
-
/* Keep all bits, except the RW bits */
new = old & (~mask | ~behavior[reg / 4].rw);
--
2.20.1
From: Russell King <[email protected]>
Add support for PCIe extended capabilities, which we just redirect to the
emulating driver.
Signed-off-by: Russell King <[email protected]>
[pali: Fix writing new value with W1C bits]
Signed-off-by: Pali Rohár <[email protected]>
---
drivers/pci/pci-bridge-emul.c | 77 +++++++++++++++++++++++------------
drivers/pci/pci-bridge-emul.h | 15 +++++++
2 files changed, 67 insertions(+), 25 deletions(-)
diff --git a/drivers/pci/pci-bridge-emul.c b/drivers/pci/pci-bridge-emul.c
index 56b2cb741498..9f4f173f0650 100644
--- a/drivers/pci/pci-bridge-emul.c
+++ b/drivers/pci/pci-bridge-emul.c
@@ -433,10 +433,16 @@ int pci_bridge_emul_conf_read(struct pci_bridge_emul *bridge, int where,
read_op = bridge->ops->read_pcie;
cfgspace = (__le32 *) &bridge->pcie_conf;
behavior = bridge->pcie_cap_regs_behavior;
- } else {
- /* Beyond our PCIe space */
+ } else if (reg < PCI_CFG_SPACE_SIZE) {
+ /* Rest of PCI space not implemented */
*value = 0;
return PCIBIOS_SUCCESSFUL;
+ } else {
+ /* PCIe extended capability space */
+ reg -= PCI_CFG_SPACE_SIZE;
+ read_op = bridge->ops->read_ext;
+ cfgspace = NULL;
+ behavior = NULL;
}
if (read_op)
@@ -444,15 +450,20 @@ int pci_bridge_emul_conf_read(struct pci_bridge_emul *bridge, int where,
else
ret = PCI_BRIDGE_EMUL_NOT_HANDLED;
- if (ret == PCI_BRIDGE_EMUL_NOT_HANDLED)
- *value = le32_to_cpu(cfgspace[reg / 4]);
+ if (ret == PCI_BRIDGE_EMUL_NOT_HANDLED) {
+ if (cfgspace)
+ *value = le32_to_cpu(cfgspace[reg / 4]);
+ else
+ *value = 0;
+ }
/*
* Make sure we never return any reserved bit with a value
* different from 0.
*/
- *value &= behavior[reg / 4].ro | behavior[reg / 4].rw |
- behavior[reg / 4].w1c;
+ if (behavior)
+ *value &= behavior[reg / 4].ro | behavior[reg / 4].rw |
+ behavior[reg / 4].w1c;
if (size == 1)
*value = (*value >> (8 * (where & 3))) & 0xff;
@@ -498,8 +509,15 @@ int pci_bridge_emul_conf_write(struct pci_bridge_emul *bridge, int where,
write_op = bridge->ops->write_pcie;
cfgspace = (__le32 *) &bridge->pcie_conf;
behavior = bridge->pcie_cap_regs_behavior;
- } else {
+ } else if (reg < PCI_CFG_SPACE_SIZE) {
+ /* Rest of PCI space not implemented */
return PCIBIOS_SUCCESSFUL;
+ } else {
+ /* PCIe extended capability space */
+ reg -= PCI_CFG_SPACE_SIZE;
+ write_op = bridge->ops->write_ext;
+ cfgspace = NULL;
+ behavior = NULL;
}
shift = (where & 0x3) * 8;
@@ -513,29 +531,38 @@ int pci_bridge_emul_conf_write(struct pci_bridge_emul *bridge, int where,
else
return PCIBIOS_BAD_REGISTER_NUMBER;
- /* Keep all bits, except the RW bits */
- new = old & (~mask | ~behavior[reg / 4].rw);
+ if (behavior) {
+ /* Keep all bits, except the RW bits */
+ new = old & (~mask | ~behavior[reg / 4].rw);
- /* Update the value of the RW bits */
- new |= (value << shift) & (behavior[reg / 4].rw & mask);
+ /* Update the value of the RW bits */
+ new |= (value << shift) & (behavior[reg / 4].rw & mask);
- /* Clear the W1C bits */
- new &= ~((value << shift) & (behavior[reg / 4].w1c & mask));
+ /* Clear the W1C bits */
+ new &= ~((value << shift) & (behavior[reg / 4].w1c & mask));
+ } else {
+ new = old & ~mask;
+ new |= (value << shift) & mask;
+ }
- /* Save the new value with the cleared W1C bits into the cfgspace */
- cfgspace[reg / 4] = cpu_to_le32(new);
+ if (cfgspace) {
+ /* Save the new value with the cleared W1C bits into the cfgspace */
+ cfgspace[reg / 4] = cpu_to_le32(new);
+ }
- /*
- * Clear the W1C bits not specified by the write mask, so that the
- * write_op() does not clear them.
- */
- new &= ~(behavior[reg / 4].w1c & ~mask);
+ if (behavior) {
+ /*
+ * Clear the W1C bits not specified by the write mask, so that the
+ * write_op() does not clear them.
+ */
+ new &= ~(behavior[reg / 4].w1c & ~mask);
- /*
- * Set the W1C bits specified by the write mask, so that write_op()
- * knows about that they are to be cleared.
- */
- new |= (value << shift) & (behavior[reg / 4].w1c & mask);
+ /*
+ * Set the W1C bits specified by the write mask, so that write_op()
+ * knows about that they are to be cleared.
+ */
+ new |= (value << shift) & (behavior[reg / 4].w1c & mask);
+ }
if (write_op)
write_op(bridge, reg, old, new, mask);
diff --git a/drivers/pci/pci-bridge-emul.h b/drivers/pci/pci-bridge-emul.h
index 4953274cac18..6b5f75b2ad02 100644
--- a/drivers/pci/pci-bridge-emul.h
+++ b/drivers/pci/pci-bridge-emul.h
@@ -90,6 +90,14 @@ struct pci_bridge_emul_ops {
*/
pci_bridge_emul_read_status_t (*read_pcie)(struct pci_bridge_emul *bridge,
int reg, u32 *value);
+
+ /*
+ * Same as ->read_base(), except it is for reading from the
+ * PCIe extended capability configuration space.
+ */
+ pci_bridge_emul_read_status_t (*read_ext)(struct pci_bridge_emul *bridge,
+ int reg, u32 *value);
+
/*
* Called when writing to the regular PCI bridge configuration
* space. old is the current value, new is the new value being
@@ -105,6 +113,13 @@ struct pci_bridge_emul_ops {
*/
void (*write_pcie)(struct pci_bridge_emul *bridge, int reg,
u32 old, u32 new, u32 mask);
+
+ /*
+ * Same as ->write_base(), except it is for writing from the
+ * PCIe extended capability configuration space.
+ */
+ void (*write_ext)(struct pci_bridge_emul *bridge, int reg,
+ u32 old, u32 new, u32 mask);
};
struct pci_bridge_reg_behavior;
--
2.20.1
If x1/x4 mode is not set correctly then link with endpoint card is not
established.
Use DTS property 'num-lanes' to deteriminate x1/x4 mode.
Signed-off-by: Pali Rohár <[email protected]>
---
drivers/pci/controller/pci-mvebu.c | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
index a075ba26cff1..0f2ec0a17874 100644
--- a/drivers/pci/controller/pci-mvebu.c
+++ b/drivers/pci/controller/pci-mvebu.c
@@ -93,6 +93,7 @@ struct mvebu_pcie_port {
void __iomem *base;
u32 port;
u32 lane;
+ bool is_x4;
int devfn;
unsigned int mem_target;
unsigned int mem_attr;
@@ -233,13 +234,25 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
{
- u32 ctrl, cmd, dev_rev, mask;
+ u32 ctrl, lnkcap, cmd, dev_rev, mask;
/* Setup PCIe controller to Root Complex mode. */
ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
ctrl |= PCIE_CTRL_RC_MODE;
mvebu_writel(port, ctrl, PCIE_CTRL_OFF);
+ /*
+ * Set Maximum Link Width to X1 or X4 in Root Port's PCIe Link
+ * Capability register. This register is defined by PCIe specification
+ * as read-only but this mvebu controller has it as read-write and must
+ * be set to number of SerDes PCIe lanes (1 or 4). If this register is
+ * not set correctly then link with endpoint card is not established.
+ */
+ lnkcap = mvebu_readl(port, PCIE_CAP_PCIEXP + PCI_EXP_LNKCAP);
+ lnkcap &= ~PCI_EXP_LNKCAP_MLW;
+ lnkcap |= (port->is_x4 ? 4 : 1) << 4;
+ mvebu_writel(port, lnkcap, PCIE_CAP_PCIEXP + PCI_EXP_LNKCAP);
+
/* Disable Root Bridge I/O space, memory space and bus mastering. */
cmd = mvebu_readl(port, PCIE_CMD_OFF);
cmd &= ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
@@ -986,6 +999,7 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
struct device *dev = &pcie->pdev->dev;
enum of_gpio_flags flags;
int reset_gpio, ret;
+ u32 num_lanes;
port->pcie = pcie;
@@ -998,6 +1012,9 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
if (of_property_read_u32(child, "marvell,pcie-lane", &port->lane))
port->lane = 0;
+ if (!of_property_read_u32(child, "num-lanes", &num_lanes) && num_lanes == 4)
+ port->is_x4 = true;
+
port->name = devm_kasprintf(dev, GFP_KERNEL, "pcie%d.%d", port->port,
port->lane);
if (!port->name) {
--
2.20.1
AER registers start at mvebu offset 0x0100. Registers PCI_ERR_ROOT_COMMAND,
PCI_ERR_ROOT_STATUS and PCI_ERR_ROOT_ERR_SRC are not supported on pre-XP
hardware and returns zeros.
Note that AER interrupt is not supported yet as mvebu emulated bridge does
not implement interrupts support at all yet.
Also remove custom macro PCIE_HEADER_LOG_4_OFF as it is unused and
correctly this register should be referenced via standard macros with
offset, e.g. as: PCIE_CAP_PCIERR_OFF + PCI_ERR_HEADER_LOG + 4.
Signed-off-by: Pali Rohár <[email protected]>
---
drivers/pci/controller/pci-mvebu.c | 67 +++++++++++++++++++++++++++++-
1 file changed, 66 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
index 811af9e6ede5..9ea2f6a7c2b0 100644
--- a/drivers/pci/controller/pci-mvebu.c
+++ b/drivers/pci/controller/pci-mvebu.c
@@ -34,7 +34,7 @@
#define PCIE_BAR_HI_OFF(n) (0x0014 + ((n) << 3))
#define PCIE_SSDEV_ID_OFF 0x002c
#define PCIE_CAP_PCIEXP 0x0060
-#define PCIE_HEADER_LOG_4_OFF 0x0128
+#define PCIE_CAP_PCIERR_OFF 0x0100
#define PCIE_BAR_CTRL_OFF(n) (0x1804 + (((n) - 1) * 4))
#define PCIE_WIN04_CTRL_OFF(n) (0x1820 + ((n) << 4))
#define PCIE_WIN04_BASE_OFF(n) (0x1824 + ((n) << 4))
@@ -603,6 +603,37 @@ mvebu_pci_bridge_emul_pcie_conf_read(struct pci_bridge_emul *bridge,
return PCI_BRIDGE_EMUL_HANDLED;
}
+static pci_bridge_emul_read_status_t
+mvebu_pci_bridge_emul_ext_conf_read(struct pci_bridge_emul *bridge,
+ int reg, u32 *value)
+{
+ struct mvebu_pcie_port *port = bridge->data;
+
+ switch (reg) {
+ case 0:
+ case PCI_ERR_UNCOR_STATUS:
+ case PCI_ERR_UNCOR_MASK:
+ case PCI_ERR_UNCOR_SEVER:
+ case PCI_ERR_COR_STATUS:
+ case PCI_ERR_COR_MASK:
+ case PCI_ERR_CAP:
+ case PCI_ERR_HEADER_LOG+0:
+ case PCI_ERR_HEADER_LOG+4:
+ case PCI_ERR_HEADER_LOG+8:
+ case PCI_ERR_HEADER_LOG+12:
+ case PCI_ERR_ROOT_COMMAND:
+ case PCI_ERR_ROOT_STATUS:
+ case PCI_ERR_ROOT_ERR_SRC:
+ *value = mvebu_readl(port, PCIE_CAP_PCIERR_OFF + reg);
+ break;
+
+ default:
+ return PCI_BRIDGE_EMUL_NOT_HANDLED;
+ }
+
+ return PCI_BRIDGE_EMUL_HANDLED;
+}
+
static void
mvebu_pci_bridge_emul_base_conf_write(struct pci_bridge_emul *bridge,
int reg, u32 old, u32 new, u32 mask)
@@ -715,11 +746,45 @@ mvebu_pci_bridge_emul_pcie_conf_write(struct pci_bridge_emul *bridge,
}
}
+static void
+mvebu_pci_bridge_emul_ext_conf_write(struct pci_bridge_emul *bridge,
+ int reg, u32 old, u32 new, u32 mask)
+{
+ struct mvebu_pcie_port *port = bridge->data;
+
+ switch (reg) {
+ /* These are W1C registers, so clear other bits */
+ case PCI_ERR_UNCOR_STATUS:
+ case PCI_ERR_COR_STATUS:
+ case PCI_ERR_ROOT_STATUS:
+ new &= mask;
+ fallthrough;
+
+ case PCI_ERR_UNCOR_MASK:
+ case PCI_ERR_UNCOR_SEVER:
+ case PCI_ERR_COR_MASK:
+ case PCI_ERR_CAP:
+ case PCI_ERR_HEADER_LOG+0:
+ case PCI_ERR_HEADER_LOG+4:
+ case PCI_ERR_HEADER_LOG+8:
+ case PCI_ERR_HEADER_LOG+12:
+ case PCI_ERR_ROOT_COMMAND:
+ case PCI_ERR_ROOT_ERR_SRC:
+ mvebu_writel(port, new, PCIE_CAP_PCIERR_OFF + reg);
+ break;
+
+ default:
+ break;
+ }
+}
+
static const struct pci_bridge_emul_ops mvebu_pci_bridge_emul_ops = {
.read_base = mvebu_pci_bridge_emul_base_conf_read,
.write_base = mvebu_pci_bridge_emul_base_conf_write,
.read_pcie = mvebu_pci_bridge_emul_pcie_conf_read,
.write_pcie = mvebu_pci_bridge_emul_pcie_conf_write,
+ .read_ext = mvebu_pci_bridge_emul_ext_conf_read,
+ .write_ext = mvebu_pci_bridge_emul_ext_conf_write,
};
/*
--
2.20.1
This adds support for legacy INTx interrupts received from other PCIe
devices and which are reported by a new INTx irq chip.
With this change, kernel can distinguish between INTA, INTB, INTC and INTD
interrupts.
Note that for this support, device tree files has to be properly adjusted
to provide "interrupts" or "interrupts-extended" property with intx
interrupt source, "interrupt-names" property with "intx" string and also
'interrupt-controller' subnode must be defined.
If device tree files do not provide these nodes then driver would work as
before.
Signed-off-by: Pali Rohár <[email protected]>
---
drivers/pci/controller/pci-mvebu.c | 185 +++++++++++++++++++++++++++--
1 file changed, 177 insertions(+), 8 deletions(-)
diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
index 1e90ab888075..dbb6ecb4cb70 100644
--- a/drivers/pci/controller/pci-mvebu.c
+++ b/drivers/pci/controller/pci-mvebu.c
@@ -54,9 +54,10 @@
PCIE_CONF_ADDR_EN)
#define PCIE_CONF_DATA_OFF 0x18fc
#define PCIE_INT_CAUSE_OFF 0x1900
+#define PCIE_INT_UNMASK_OFF 0x1910
+#define PCIE_INT_INTX(i) BIT(24+i)
#define PCIE_INT_PM_PME BIT(28)
-#define PCIE_MASK_OFF 0x1910
-#define PCIE_MASK_ENABLE_INTS 0x0f000000
+#define PCIE_INT_ALL_MASK GENMASK(31, 0)
#define PCIE_CTRL_OFF 0x1a00
#define PCIE_CTRL_X1_MODE 0x0001
#define PCIE_CTRL_RC_MODE BIT(1)
@@ -110,6 +111,9 @@ struct mvebu_pcie_port {
struct mvebu_pcie_window iowin;
u32 saved_pcie_stat;
struct resource regs;
+ struct irq_domain *intx_irq_domain;
+ raw_spinlock_t irq_lock;
+ int intx_irq;
};
static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
@@ -235,7 +239,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
{
- u32 ctrl, lnkcap, cmd, dev_rev, mask;
+ u32 ctrl, lnkcap, cmd, dev_rev, unmask;
/* Setup PCIe controller to Root Complex mode. */
ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
@@ -288,10 +292,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
/* Point PCIe unit MBUS decode windows to DRAM space. */
mvebu_pcie_setup_wins(port);
- /* Enable interrupt lines A-D. */
- mask = mvebu_readl(port, PCIE_MASK_OFF);
- mask |= PCIE_MASK_ENABLE_INTS;
- mvebu_writel(port, mask, PCIE_MASK_OFF);
+ /* Mask all interrupt sources. */
+ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
+
+ /* Clear all interrupt causes. */
+ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
+
+ if (port->intx_irq <= 0) {
+ /*
+ * When neither "summary" interrupt, nor "intx" interrupt was
+ * specified in DT then unmask all legacy INTx interrupts as in
+ * this case driver does not provide a way for masking and
+ * unmasking of individual legacy INTx interrupts. In this case
+ * all interrupts, including legacy INTx are reported via one
+ * shared GIC source and therefore kernel cannot distinguish
+ * which individual legacy INTx was triggered. These interrupts
+ * are shared, so it should not cause any issue. Just
+ * performance penalty as every PCIe interrupt handler needs to
+ * be called when some interrupt is triggered.
+ */
+ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+ unmask |= PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
+ PCIE_INT_INTX(2) | PCIE_INT_INTX(3);
+ mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
+ }
}
static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
@@ -924,6 +948,108 @@ static struct pci_ops mvebu_pcie_ops = {
.write = mvebu_pcie_wr_conf,
};
+static void mvebu_pcie_intx_irq_mask(struct irq_data *d)
+{
+ struct mvebu_pcie_port *port = d->domain->host_data;
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+ unsigned long flags;
+ u32 unmask;
+
+ raw_spin_lock_irqsave(&port->irq_lock, flags);
+ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+ unmask &= ~PCIE_INT_INTX(hwirq);
+ mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
+ raw_spin_unlock_irqrestore(&port->irq_lock, flags);
+}
+
+static void mvebu_pcie_intx_irq_unmask(struct irq_data *d)
+{
+ struct mvebu_pcie_port *port = d->domain->host_data;
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+ unsigned long flags;
+ u32 unmask;
+
+ raw_spin_lock_irqsave(&port->irq_lock, flags);
+ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+ unmask |= PCIE_INT_INTX(hwirq);
+ mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
+ raw_spin_unlock_irqrestore(&port->irq_lock, flags);
+}
+
+static struct irq_chip intx_irq_chip = {
+ .name = "mvebu-INTx",
+ .irq_mask = mvebu_pcie_intx_irq_mask,
+ .irq_unmask = mvebu_pcie_intx_irq_unmask,
+};
+
+static int mvebu_pcie_intx_irq_map(struct irq_domain *h,
+ unsigned int virq, irq_hw_number_t hwirq)
+{
+ struct mvebu_pcie_port *port = h->host_data;
+
+ irq_set_status_flags(virq, IRQ_LEVEL);
+ irq_set_chip_and_handler(virq, &intx_irq_chip, handle_level_irq);
+ irq_set_chip_data(virq, port);
+
+ return 0;
+}
+
+static const struct irq_domain_ops mvebu_pcie_intx_irq_domain_ops = {
+ .map = mvebu_pcie_intx_irq_map,
+ .xlate = irq_domain_xlate_onecell,
+};
+
+static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
+{
+ struct device *dev = &port->pcie->pdev->dev;
+ struct device_node *pcie_intc_node;
+
+ raw_spin_lock_init(&port->irq_lock);
+
+ pcie_intc_node = of_get_next_child(port->dn, NULL);
+ if (!pcie_intc_node) {
+ dev_err(dev, "No PCIe Intc node found for %s\n", port->name);
+ return -ENODEV;
+ }
+
+ port->intx_irq_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX,
+ &mvebu_pcie_intx_irq_domain_ops,
+ port);
+ of_node_put(pcie_intc_node);
+ if (!port->intx_irq_domain) {
+ dev_err(dev, "Failed to get INTx IRQ domain for %s\n", port->name);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void mvebu_pcie_irq_handler(struct irq_desc *desc)
+{
+ struct mvebu_pcie_port *port = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ struct device *dev = &port->pcie->pdev->dev;
+ u32 cause, unmask, status;
+ int i;
+
+ chained_irq_enter(chip, desc);
+
+ cause = mvebu_readl(port, PCIE_INT_CAUSE_OFF);
+ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+ status = cause & unmask;
+
+ /* Process legacy INTx interrupts */
+ for (i = 0; i < PCI_NUM_INTX; i++) {
+ if (!(status & PCIE_INT_INTX(i)))
+ continue;
+
+ if (generic_handle_domain_irq(port->intx_irq_domain, i) == -EINVAL)
+ dev_err_ratelimited(dev, "unexpected INT%c IRQ\n", (char)i+'A');
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
static int mvebu_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
{
/* Interrupt support on mvebu emulated bridges is not implemented yet */
@@ -1121,6 +1247,21 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
port->io_attr = -1;
}
+ /*
+ * Old DT bindings do not contain "intx" interrupt
+ * so do not fail probing driver when interrupt does not exist.
+ */
+ port->intx_irq = of_irq_get_byname(child, "intx");
+ if (port->intx_irq == -EPROBE_DEFER) {
+ ret = port->intx_irq;
+ goto err;
+ }
+ if (port->intx_irq <= 0) {
+ dev_warn(dev, "%s: legacy INTx interrupts cannot be masked individually, "
+ "%pOF does not contain intx interrupt\n",
+ port->name, child);
+ }
+
reset_gpio = of_get_named_gpio_flags(child, "reset-gpios", 0, &flags);
if (reset_gpio == -EPROBE_DEFER) {
ret = reset_gpio;
@@ -1317,6 +1458,7 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
for (i = 0; i < pcie->nports; i++) {
struct mvebu_pcie_port *port = &pcie->ports[i];
+ int irq = port->intx_irq;
child = port->dn;
if (!child)
@@ -1344,6 +1486,22 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
continue;
}
+ if (irq > 0) {
+ ret = mvebu_pcie_init_irq_domain(port);
+ if (ret) {
+ dev_err(dev, "%s: cannot init irq domain\n",
+ port->name);
+ pci_bridge_emul_cleanup(&port->bridge);
+ devm_iounmap(dev, port->base);
+ port->base = NULL;
+ mvebu_pcie_powerdown(port);
+ continue;
+ }
+ irq_set_chained_handler_and_data(irq,
+ mvebu_pcie_irq_handler,
+ port);
+ }
+
/*
* PCIe topology exported by mvebu hw is quite complicated. In
* reality has something like N fully independent host bridges
@@ -1448,6 +1606,7 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
for (i = 0; i < pcie->nports; i++) {
struct mvebu_pcie_port *port = &pcie->ports[i];
+ int irq = port->intx_irq;
if (!port->base)
continue;
@@ -1458,7 +1617,17 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
mvebu_writel(port, cmd, PCIE_CMD_OFF);
/* Mask all interrupt sources. */
- mvebu_writel(port, 0, PCIE_MASK_OFF);
+ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
+
+ /* Clear all interrupt causes. */
+ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
+
+ if (irq > 0)
+ irq_set_chained_handler_and_data(irq, NULL, NULL);
+
+ /* Remove IRQ domains. */
+ if (port->intx_irq_domain)
+ irq_domain_remove(port->intx_irq_domain);
/* Free config space for emulated root bridge. */
pci_bridge_emul_cleanup(&port->bridge);
--
2.20.1
Signed-off-by: Pali Rohár <[email protected]>
Acked-by: Rob Herring <[email protected]>
---
Documentation/devicetree/bindings/pci/mvebu-pci.txt | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Documentation/devicetree/bindings/pci/mvebu-pci.txt b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
index 24225852bce0..6d022a9d36ee 100644
--- a/Documentation/devicetree/bindings/pci/mvebu-pci.txt
+++ b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
@@ -81,6 +81,11 @@ and the following optional properties:
- reset-gpios: optional GPIO to PERST#
- reset-delay-us: delay in us to wait after reset de-assertion, if not
specified will default to 100ms, as required by the PCIe specification.
+- interrupt-names: list of interrupt names, supported are:
+ - "intx" - interrupt line triggered by one of the legacy interrupt
+- interrupts or interrupts-extended: List of the interrupt sources which
+ corresponding to the "interrupt-names". If non-empty then also additional
+ 'interrupt-controller' subnode must be defined.
Example:
--
2.20.1
With this change legacy INTA, INTB, INTC and INTD interrupts are reported
separately and not mixed into one Linux virq source anymore.
Signed-off-by: Pali Rohár <[email protected]>
---
arch/arm/boot/dts/armada-385.dtsi | 52 ++++++++++++++++++++++++++-----
1 file changed, 44 insertions(+), 8 deletions(-)
diff --git a/arch/arm/boot/dts/armada-385.dtsi b/arch/arm/boot/dts/armada-385.dtsi
index f0022d10c715..83392b92dae2 100644
--- a/arch/arm/boot/dts/armada-385.dtsi
+++ b/arch/arm/boot/dts/armada-385.dtsi
@@ -69,16 +69,25 @@
reg = <0x0800 0 0 0 0>;
#address-cells = <3>;
#size-cells = <2>;
+ interrupt-names = "intx";
+ interrupts-extended = <&gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
#interrupt-cells = <1>;
ranges = <0x82000000 0 0 0x82000000 0x1 0 1 0
0x81000000 0 0 0x81000000 0x1 0 1 0>;
bus-range = <0x00 0xff>;
- interrupt-map-mask = <0 0 0 0>;
- interrupt-map = <0 0 0 0 &gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-map-mask = <0 0 0 7>;
+ interrupt-map = <0 0 0 1 &pcie1_intc 0>,
+ <0 0 0 2 &pcie1_intc 1>,
+ <0 0 0 3 &pcie1_intc 2>,
+ <0 0 0 4 &pcie1_intc 3>;
marvell,pcie-port = <0>;
marvell,pcie-lane = <0>;
clocks = <&gateclk 8>;
status = "disabled";
+ pcie1_intc: interrupt-controller {
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ };
};
/* x1 port */
@@ -88,16 +97,25 @@
reg = <0x1000 0 0 0 0>;
#address-cells = <3>;
#size-cells = <2>;
+ interrupt-names = "intx";
+ interrupts-extended = <&gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
#interrupt-cells = <1>;
ranges = <0x82000000 0 0 0x82000000 0x2 0 1 0
0x81000000 0 0 0x81000000 0x2 0 1 0>;
bus-range = <0x00 0xff>;
- interrupt-map-mask = <0 0 0 0>;
- interrupt-map = <0 0 0 0 &gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-map-mask = <0 0 0 7>;
+ interrupt-map = <0 0 0 1 &pcie2_intc 0>,
+ <0 0 0 2 &pcie2_intc 1>,
+ <0 0 0 3 &pcie2_intc 2>,
+ <0 0 0 4 &pcie2_intc 3>;
marvell,pcie-port = <1>;
marvell,pcie-lane = <0>;
clocks = <&gateclk 5>;
status = "disabled";
+ pcie2_intc: interrupt-controller {
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ };
};
/* x1 port */
@@ -107,16 +125,25 @@
reg = <0x1800 0 0 0 0>;
#address-cells = <3>;
#size-cells = <2>;
+ interrupt-names = "intx";
+ interrupts-extended = <&gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
#interrupt-cells = <1>;
ranges = <0x82000000 0 0 0x82000000 0x3 0 1 0
0x81000000 0 0 0x81000000 0x3 0 1 0>;
bus-range = <0x00 0xff>;
- interrupt-map-mask = <0 0 0 0>;
- interrupt-map = <0 0 0 0 &gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-map-mask = <0 0 0 7>;
+ interrupt-map = <0 0 0 1 &pcie3_intc 0>,
+ <0 0 0 2 &pcie3_intc 1>,
+ <0 0 0 3 &pcie3_intc 2>,
+ <0 0 0 4 &pcie3_intc 3>;
marvell,pcie-port = <2>;
marvell,pcie-lane = <0>;
clocks = <&gateclk 6>;
status = "disabled";
+ pcie3_intc: interrupt-controller {
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ };
};
/*
@@ -129,16 +156,25 @@
reg = <0x2000 0 0 0 0>;
#address-cells = <3>;
#size-cells = <2>;
+ interrupt-names = "intx";
+ interrupts-extended = <&gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
#interrupt-cells = <1>;
ranges = <0x82000000 0 0 0x82000000 0x4 0 1 0
0x81000000 0 0 0x81000000 0x4 0 1 0>;
bus-range = <0x00 0xff>;
- interrupt-map-mask = <0 0 0 0>;
- interrupt-map = <0 0 0 0 &gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-map-mask = <0 0 0 7>;
+ interrupt-map = <0 0 0 1 &pcie4_intc 0>,
+ <0 0 0 2 &pcie4_intc 1>,
+ <0 0 0 3 &pcie4_intc 2>,
+ <0 0 0 4 &pcie4_intc 3>;
marvell,pcie-port = <3>;
marvell,pcie-lane = <0>;
clocks = <&gateclk 7>;
status = "disabled";
+ pcie4_intc: interrupt-controller {
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ };
};
};
};
--
2.20.1
Split struct pci_ops between ops and child_ops. Member ops is used for
accessing PCIe Root Ports via pci-bridge-emul.c driver and child_ops for
accessing real PCIe cards.
There is no need to mix these two struct pci_ops into one as PCI core code
already provides separate callbacks via bridge->ops and bridge->child_ops.
Signed-off-by: Pali Rohár <[email protected]>
---
drivers/pci/controller/pci-mvebu.c | 82 ++++++++++++++++--------------
1 file changed, 44 insertions(+), 38 deletions(-)
diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
index 9ea2f6a7c2b0..1e90ab888075 100644
--- a/drivers/pci/controller/pci-mvebu.c
+++ b/drivers/pci/controller/pci-mvebu.c
@@ -294,11 +294,29 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
mvebu_writel(port, mask, PCIE_MASK_OFF);
}
-static int mvebu_pcie_hw_rd_conf(struct mvebu_pcie_port *port,
- struct pci_bus *bus,
- u32 devfn, int where, int size, u32 *val)
+static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
+ struct pci_bus *bus,
+ int devfn);
+
+static int mvebu_pcie_child_rd_conf(struct pci_bus *bus, u32 devfn, int where,
+ int size, u32 *val)
{
- void __iomem *conf_data = port->base + PCIE_CONF_DATA_OFF;
+ struct mvebu_pcie *pcie = bus->sysdata;
+ struct mvebu_pcie_port *port;
+ void __iomem *conf_data;
+
+ port = mvebu_pcie_find_port(pcie, bus, devfn);
+ if (!port) {
+ *val = 0xffffffff;
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ }
+
+ if (!mvebu_pcie_link_up(port)) {
+ *val = 0xffffffff;
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ }
+
+ conf_data = port->base + PCIE_CONF_DATA_OFF;
mvebu_writel(port, PCIE_CONF_ADDR(bus->number, devfn, where),
PCIE_CONF_ADDR_OFF);
@@ -321,11 +339,21 @@ static int mvebu_pcie_hw_rd_conf(struct mvebu_pcie_port *port,
return PCIBIOS_SUCCESSFUL;
}
-static int mvebu_pcie_hw_wr_conf(struct mvebu_pcie_port *port,
- struct pci_bus *bus,
- u32 devfn, int where, int size, u32 val)
+static int mvebu_pcie_child_wr_conf(struct pci_bus *bus, u32 devfn,
+ int where, int size, u32 val)
{
- void __iomem *conf_data = port->base + PCIE_CONF_DATA_OFF;
+ struct mvebu_pcie *pcie = bus->sysdata;
+ struct mvebu_pcie_port *port;
+ void __iomem *conf_data;
+
+ port = mvebu_pcie_find_port(pcie, bus, devfn);
+ if (!port)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ if (!mvebu_pcie_link_up(port))
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ conf_data = port->base + PCIE_CONF_DATA_OFF;
mvebu_writel(port, PCIE_CONF_ADDR(bus->number, devfn, where),
PCIE_CONF_ADDR_OFF);
@@ -347,6 +375,11 @@ static int mvebu_pcie_hw_wr_conf(struct mvebu_pcie_port *port,
return PCIBIOS_SUCCESSFUL;
}
+static struct pci_ops mvebu_pcie_child_ops = {
+ .read = mvebu_pcie_child_rd_conf,
+ .write = mvebu_pcie_child_wr_conf,
+};
+
/*
* Remove windows, starting from the largest ones to the smallest
* ones.
@@ -862,25 +895,12 @@ static int mvebu_pcie_wr_conf(struct pci_bus *bus, u32 devfn,
{
struct mvebu_pcie *pcie = bus->sysdata;
struct mvebu_pcie_port *port;
- int ret;
port = mvebu_pcie_find_port(pcie, bus, devfn);
if (!port)
return PCIBIOS_DEVICE_NOT_FOUND;
- /* Access the emulated PCI-to-PCI bridge */
- if (bus->number == 0)
- return pci_bridge_emul_conf_write(&port->bridge, where,
- size, val);
-
- if (!mvebu_pcie_link_up(port))
- return PCIBIOS_DEVICE_NOT_FOUND;
-
- /* Access the real PCIe interface */
- ret = mvebu_pcie_hw_wr_conf(port, bus, devfn,
- where, size, val);
-
- return ret;
+ return pci_bridge_emul_conf_write(&port->bridge, where, size, val);
}
/* PCI configuration space read function */
@@ -889,7 +909,6 @@ static int mvebu_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where,
{
struct mvebu_pcie *pcie = bus->sysdata;
struct mvebu_pcie_port *port;
- int ret;
port = mvebu_pcie_find_port(pcie, bus, devfn);
if (!port) {
@@ -897,21 +916,7 @@ static int mvebu_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where,
return PCIBIOS_DEVICE_NOT_FOUND;
}
- /* Access the emulated PCI-to-PCI bridge */
- if (bus->number == 0)
- return pci_bridge_emul_conf_read(&port->bridge, where,
- size, val);
-
- if (!mvebu_pcie_link_up(port)) {
- *val = 0xffffffff;
- return PCIBIOS_DEVICE_NOT_FOUND;
- }
-
- /* Access the real PCIe interface */
- ret = mvebu_pcie_hw_rd_conf(port, bus, devfn,
- where, size, val);
-
- return ret;
+ return pci_bridge_emul_conf_read(&port->bridge, where, size, val);
}
static struct pci_ops mvebu_pcie_ops = {
@@ -1421,6 +1426,7 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
bridge->sysdata = pcie;
bridge->ops = &mvebu_pcie_ops;
+ bridge->child_ops = &mvebu_pcie_child_ops;
bridge->align_resource = mvebu_pcie_align_resource;
bridge->map_irq = mvebu_pcie_map_irq;
--
2.20.1
Register with Subsystem Device/Vendor ID is at offset 0x2c. Export is via
emulated bridge.
After this change Subsystem ID is visible in lspci output at line:
Capabilities: [40] Subsystem
Signed-off-by: Pali Rohár <[email protected]>
---
drivers/pci/controller/pci-mvebu.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
index 0f2ec0a17874..811af9e6ede5 100644
--- a/drivers/pci/controller/pci-mvebu.c
+++ b/drivers/pci/controller/pci-mvebu.c
@@ -32,6 +32,7 @@
#define PCIE_DEV_REV_OFF 0x0008
#define PCIE_BAR_LO_OFF(n) (0x0010 + ((n) << 3))
#define PCIE_BAR_HI_OFF(n) (0x0014 + ((n) << 3))
+#define PCIE_SSDEV_ID_OFF 0x002c
#define PCIE_CAP_PCIEXP 0x0060
#define PCIE_HEADER_LOG_4_OFF 0x0128
#define PCIE_BAR_CTRL_OFF(n) (0x1804 + (((n) - 1) * 4))
@@ -731,6 +732,7 @@ static int mvebu_pci_bridge_emul_init(struct mvebu_pcie_port *port)
struct pci_bridge_emul *bridge = &port->bridge;
u32 dev_id = mvebu_readl(port, PCIE_DEV_ID_OFF);
u32 dev_rev = mvebu_readl(port, PCIE_DEV_REV_OFF);
+ u32 ssdev_id = mvebu_readl(port, PCIE_SSDEV_ID_OFF);
u32 pcie_cap = mvebu_readl(port, PCIE_CAP_PCIEXP);
u8 pcie_cap_ver = ((pcie_cap >> 16) & PCI_EXP_FLAGS_VERS);
@@ -752,6 +754,8 @@ static int mvebu_pci_bridge_emul_init(struct mvebu_pcie_port *port)
*/
bridge->pcie_conf.cap = cpu_to_le16(pcie_cap_ver);
+ bridge->subsystem_vendor_id = ssdev_id & 0xffff;
+ bridge->subsystem_id = ssdev_id >> 16;
bridge->has_pcie = true;
bridge->data = port;
bridge->ops = &mvebu_pci_bridge_emul_ops;
--
2.20.1
On Wed, 12 Jan 2022 16:18:12 +0100
Pali Rohár <[email protected]> wrote:
> Signed-off-by: Pali Rohár <[email protected]>
> Acked-by: Rob Herring <[email protected]>
> ---
> Documentation/devicetree/bindings/pci/mvebu-pci.txt | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/pci/mvebu-pci.txt b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
> index 24225852bce0..6d022a9d36ee 100644
> --- a/Documentation/devicetree/bindings/pci/mvebu-pci.txt
> +++ b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
> @@ -81,6 +81,11 @@ and the following optional properties:
> - reset-gpios: optional GPIO to PERST#
> - reset-delay-us: delay in us to wait after reset de-assertion, if not
> specified will default to 100ms, as required by the PCIe specification.
> +- interrupt-names: list of interrupt names, supported are:
> + - "intx" - interrupt line triggered by one of the legacy interrupt
> +- interrupts or interrupts-extended: List of the interrupt sources which
> + corresponding to the "interrupt-names". If non-empty then also additional
> + 'interrupt-controller' subnode must be defined.
>
> Example:
>
Empty commit message. At least add something like this:
Document the following additional properties in the mvebu-pci DT
binding:
- interrupt-names
- interrupts / interrupts-extended
Marek
On Thursday 20 January 2022 10:49:05 Rob Herring wrote:
> On Wed, Jan 12, 2022 at 04:18:11PM +0100, Pali Rohár wrote:
> > Split struct pci_ops between ops and child_ops. Member ops is used for
> > accessing PCIe Root Ports via pci-bridge-emul.c driver and child_ops for
> > accessing real PCIe cards.
> >
> > There is no need to mix these two struct pci_ops into one as PCI core code
> > already provides separate callbacks via bridge->ops and bridge->child_ops.
> >
> > Signed-off-by: Pali Rohár <[email protected]>
> > ---
> > drivers/pci/controller/pci-mvebu.c | 82 ++++++++++++++++--------------
> > 1 file changed, 44 insertions(+), 38 deletions(-)
> >
> > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > index 9ea2f6a7c2b0..1e90ab888075 100644
> > --- a/drivers/pci/controller/pci-mvebu.c
> > +++ b/drivers/pci/controller/pci-mvebu.c
> > @@ -294,11 +294,29 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > mvebu_writel(port, mask, PCIE_MASK_OFF);
> > }
> >
> > -static int mvebu_pcie_hw_rd_conf(struct mvebu_pcie_port *port,
> > - struct pci_bus *bus,
> > - u32 devfn, int where, int size, u32 *val)
> > +static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
> > + struct pci_bus *bus,
> > + int devfn);
> > +
> > +static int mvebu_pcie_child_rd_conf(struct pci_bus *bus, u32 devfn, int where,
> > + int size, u32 *val)
> > {
> > - void __iomem *conf_data = port->base + PCIE_CONF_DATA_OFF;
> > + struct mvebu_pcie *pcie = bus->sysdata;
> > + struct mvebu_pcie_port *port;
> > + void __iomem *conf_data;
> > +
> > + port = mvebu_pcie_find_port(pcie, bus, devfn);
> > + if (!port) {
> > + *val = 0xffffffff;
> > + return PCIBIOS_DEVICE_NOT_FOUND;
> > + }
> > +
> > + if (!mvebu_pcie_link_up(port)) {
> > + *val = 0xffffffff;
> > + return PCIBIOS_DEVICE_NOT_FOUND;
> > + }
> > +
> > + conf_data = port->base + PCIE_CONF_DATA_OFF;
> >
> > mvebu_writel(port, PCIE_CONF_ADDR(bus->number, devfn, where),
> > PCIE_CONF_ADDR_OFF);
> > @@ -321,11 +339,21 @@ static int mvebu_pcie_hw_rd_conf(struct mvebu_pcie_port *port,
> > return PCIBIOS_SUCCESSFUL;
> > }
> >
> > -static int mvebu_pcie_hw_wr_conf(struct mvebu_pcie_port *port,
> > - struct pci_bus *bus,
> > - u32 devfn, int where, int size, u32 val)
> > +static int mvebu_pcie_child_wr_conf(struct pci_bus *bus, u32 devfn,
> > + int where, int size, u32 val)
> > {
> > - void __iomem *conf_data = port->base + PCIE_CONF_DATA_OFF;
> > + struct mvebu_pcie *pcie = bus->sysdata;
> > + struct mvebu_pcie_port *port;
> > + void __iomem *conf_data;
> > +
>
> > + port = mvebu_pcie_find_port(pcie, bus, devfn);
> > + if (!port)
> > + return PCIBIOS_DEVICE_NOT_FOUND;
> > +
> > + if (!mvebu_pcie_link_up(port))
> > + return PCIBIOS_DEVICE_NOT_FOUND;
> > +
> > + conf_data = port->base + PCIE_CONF_DATA_OFF;
>
> Again, the same setup code in read and write is a sign to use
> .map_bus(). You can copy it from my version I pointed you to.
>
> Rob
I'm planning to do other cleanup in followup patches. But there are too
many mvebu and aardvark patches on the list waiting, and I do not want
to send another batch.
On Wed, Jan 12, 2022 at 04:18:08PM +0100, Pali Roh?r wrote:
> If x1/x4 mode is not set correctly then link with endpoint card is not
> established.
>
> Use DTS property 'num-lanes' to deteriminate x1/x4 mode.
>
> Signed-off-by: Pali Roh?r <[email protected]>
> ---
> drivers/pci/controller/pci-mvebu.c | 19 ++++++++++++++++++-
> 1 file changed, 18 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> index a075ba26cff1..0f2ec0a17874 100644
> --- a/drivers/pci/controller/pci-mvebu.c
> +++ b/drivers/pci/controller/pci-mvebu.c
> @@ -93,6 +93,7 @@ struct mvebu_pcie_port {
> void __iomem *base;
> u32 port;
> u32 lane;
> + bool is_x4;
I would just store the number of lanes.
> int devfn;
> unsigned int mem_target;
> unsigned int mem_attr;
> @@ -233,13 +234,25 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
>
> static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> {
> - u32 ctrl, cmd, dev_rev, mask;
> + u32 ctrl, lnkcap, cmd, dev_rev, mask;
>
> /* Setup PCIe controller to Root Complex mode. */
> ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> ctrl |= PCIE_CTRL_RC_MODE;
> mvebu_writel(port, ctrl, PCIE_CTRL_OFF);
>
> + /*
> + * Set Maximum Link Width to X1 or X4 in Root Port's PCIe Link
> + * Capability register. This register is defined by PCIe specification
> + * as read-only but this mvebu controller has it as read-write and must
> + * be set to number of SerDes PCIe lanes (1 or 4). If this register is
> + * not set correctly then link with endpoint card is not established.
> + */
> + lnkcap = mvebu_readl(port, PCIE_CAP_PCIEXP + PCI_EXP_LNKCAP);
> + lnkcap &= ~PCI_EXP_LNKCAP_MLW;
> + lnkcap |= (port->is_x4 ? 4 : 1) << 4;
then this is just: lanes << 4
> + mvebu_writel(port, lnkcap, PCIE_CAP_PCIEXP + PCI_EXP_LNKCAP);
> +
> /* Disable Root Bridge I/O space, memory space and bus mastering. */
> cmd = mvebu_readl(port, PCIE_CMD_OFF);
> cmd &= ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
> @@ -986,6 +999,7 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
> struct device *dev = &pcie->pdev->dev;
> enum of_gpio_flags flags;
> int reset_gpio, ret;
> + u32 num_lanes;
>
> port->pcie = pcie;
>
> @@ -998,6 +1012,9 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
> if (of_property_read_u32(child, "marvell,pcie-lane", &port->lane))
> port->lane = 0;
>
> + if (!of_property_read_u32(child, "num-lanes", &num_lanes) && num_lanes == 4)
> + port->is_x4 = true;
And this can be:
num_lanes = 1;
of_property_read_u32(child, "num-lanes", &num_lanes);
If you want to validate the DT is only 1 or 4, make the DT schema do
that.
> +
> port->name = devm_kasprintf(dev, GFP_KERNEL, "pcie%d.%d", port->port,
> port->lane);
> if (!port->name) {
> --
> 2.20.1
>
>
On Wed, Jan 12, 2022 at 04:18:11PM +0100, Pali Roh?r wrote:
> Split struct pci_ops between ops and child_ops. Member ops is used for
> accessing PCIe Root Ports via pci-bridge-emul.c driver and child_ops for
> accessing real PCIe cards.
>
> There is no need to mix these two struct pci_ops into one as PCI core code
> already provides separate callbacks via bridge->ops and bridge->child_ops.
>
> Signed-off-by: Pali Roh?r <[email protected]>
> ---
> drivers/pci/controller/pci-mvebu.c | 82 ++++++++++++++++--------------
> 1 file changed, 44 insertions(+), 38 deletions(-)
>
> diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> index 9ea2f6a7c2b0..1e90ab888075 100644
> --- a/drivers/pci/controller/pci-mvebu.c
> +++ b/drivers/pci/controller/pci-mvebu.c
> @@ -294,11 +294,29 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> mvebu_writel(port, mask, PCIE_MASK_OFF);
> }
>
> -static int mvebu_pcie_hw_rd_conf(struct mvebu_pcie_port *port,
> - struct pci_bus *bus,
> - u32 devfn, int where, int size, u32 *val)
> +static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
> + struct pci_bus *bus,
> + int devfn);
> +
> +static int mvebu_pcie_child_rd_conf(struct pci_bus *bus, u32 devfn, int where,
> + int size, u32 *val)
> {
> - void __iomem *conf_data = port->base + PCIE_CONF_DATA_OFF;
> + struct mvebu_pcie *pcie = bus->sysdata;
> + struct mvebu_pcie_port *port;
> + void __iomem *conf_data;
> +
> + port = mvebu_pcie_find_port(pcie, bus, devfn);
> + if (!port) {
> + *val = 0xffffffff;
> + return PCIBIOS_DEVICE_NOT_FOUND;
> + }
> +
> + if (!mvebu_pcie_link_up(port)) {
> + *val = 0xffffffff;
> + return PCIBIOS_DEVICE_NOT_FOUND;
> + }
> +
> + conf_data = port->base + PCIE_CONF_DATA_OFF;
>
> mvebu_writel(port, PCIE_CONF_ADDR(bus->number, devfn, where),
> PCIE_CONF_ADDR_OFF);
> @@ -321,11 +339,21 @@ static int mvebu_pcie_hw_rd_conf(struct mvebu_pcie_port *port,
> return PCIBIOS_SUCCESSFUL;
> }
>
> -static int mvebu_pcie_hw_wr_conf(struct mvebu_pcie_port *port,
> - struct pci_bus *bus,
> - u32 devfn, int where, int size, u32 val)
> +static int mvebu_pcie_child_wr_conf(struct pci_bus *bus, u32 devfn,
> + int where, int size, u32 val)
> {
> - void __iomem *conf_data = port->base + PCIE_CONF_DATA_OFF;
> + struct mvebu_pcie *pcie = bus->sysdata;
> + struct mvebu_pcie_port *port;
> + void __iomem *conf_data;
> +
> + port = mvebu_pcie_find_port(pcie, bus, devfn);
> + if (!port)
> + return PCIBIOS_DEVICE_NOT_FOUND;
> +
> + if (!mvebu_pcie_link_up(port))
> + return PCIBIOS_DEVICE_NOT_FOUND;
> +
> + conf_data = port->base + PCIE_CONF_DATA_OFF;
Again, the same setup code in read and write is a sign to use
.map_bus(). You can copy it from my version I pointed you to.
Rob
On Thursday 20 January 2022 11:09:17 Rob Herring wrote:
> On Wed, Jan 12, 2022 at 04:18:08PM +0100, Pali Rohár wrote:
> > If x1/x4 mode is not set correctly then link with endpoint card is not
> > established.
> >
> > Use DTS property 'num-lanes' to deteriminate x1/x4 mode.
> >
> > Signed-off-by: Pali Rohár <[email protected]>
> > ---
> > drivers/pci/controller/pci-mvebu.c | 19 ++++++++++++++++++-
> > 1 file changed, 18 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > index a075ba26cff1..0f2ec0a17874 100644
> > --- a/drivers/pci/controller/pci-mvebu.c
> > +++ b/drivers/pci/controller/pci-mvebu.c
> > @@ -93,6 +93,7 @@ struct mvebu_pcie_port {
> > void __iomem *base;
> > u32 port;
> > u32 lane;
> > + bool is_x4;
>
> I would just store the number of lanes.
>
> > int devfn;
> > unsigned int mem_target;
> > unsigned int mem_attr;
> > @@ -233,13 +234,25 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
> >
> > static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > {
> > - u32 ctrl, cmd, dev_rev, mask;
> > + u32 ctrl, lnkcap, cmd, dev_rev, mask;
> >
> > /* Setup PCIe controller to Root Complex mode. */
> > ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> > ctrl |= PCIE_CTRL_RC_MODE;
> > mvebu_writel(port, ctrl, PCIE_CTRL_OFF);
> >
> > + /*
> > + * Set Maximum Link Width to X1 or X4 in Root Port's PCIe Link
> > + * Capability register. This register is defined by PCIe specification
> > + * as read-only but this mvebu controller has it as read-write and must
> > + * be set to number of SerDes PCIe lanes (1 or 4). If this register is
> > + * not set correctly then link with endpoint card is not established.
> > + */
> > + lnkcap = mvebu_readl(port, PCIE_CAP_PCIEXP + PCI_EXP_LNKCAP);
> > + lnkcap &= ~PCI_EXP_LNKCAP_MLW;
> > + lnkcap |= (port->is_x4 ? 4 : 1) << 4;
>
> then this is just: lanes << 4
As only 1 and 4 are valid valid values, I chose this style (is_x4) to
ensure that no other (invalid) value is written into mvebu register.
Setting width to smaller number (if incorrect value is provided) still
allows controller to work and link should be negotiated. So setting 1 is
a sane default when controller does not have configured 4 SerDes lines
to PCIe.
> > + mvebu_writel(port, lnkcap, PCIE_CAP_PCIEXP + PCI_EXP_LNKCAP);
> > +
> > /* Disable Root Bridge I/O space, memory space and bus mastering. */
> > cmd = mvebu_readl(port, PCIE_CMD_OFF);
> > cmd &= ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
> > @@ -986,6 +999,7 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
> > struct device *dev = &pcie->pdev->dev;
> > enum of_gpio_flags flags;
> > int reset_gpio, ret;
> > + u32 num_lanes;
> >
> > port->pcie = pcie;
> >
> > @@ -998,6 +1012,9 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
> > if (of_property_read_u32(child, "marvell,pcie-lane", &port->lane))
> > port->lane = 0;
> >
> > + if (!of_property_read_u32(child, "num-lanes", &num_lanes) && num_lanes == 4)
> > + port->is_x4 = true;
>
> And this can be:
>
> num_lanes = 1;
> of_property_read_u32(child, "num-lanes", &num_lanes);
>
> If you want to validate the DT is only 1 or 4, make the DT schema do
> that.
The problem is that there is no schema for this platform and PCIe yet.
So adding it would mean to first convert everything to schema and then
this constrain can be expressed in schema.
I'm planning to look at possibility to write schema for this platform
but I do not want to do it before open issues with representation of
other pcie properties in dts schema are resolved.
>
> > +
> > port->name = devm_kasprintf(dev, GFP_KERNEL, "pcie%d.%d", port->port,
> > port->lane);
> > if (!port->name) {
> > --
> > 2.20.1
> >
> >
On Thu, Jan 20, 2022 at 10:55 AM Pali Rohár <[email protected]> wrote:
>
> On Thursday 20 January 2022 10:49:05 Rob Herring wrote:
> > On Wed, Jan 12, 2022 at 04:18:11PM +0100, Pali Rohár wrote:
> > > Split struct pci_ops between ops and child_ops. Member ops is used for
> > > accessing PCIe Root Ports via pci-bridge-emul.c driver and child_ops for
> > > accessing real PCIe cards.
> > >
> > > There is no need to mix these two struct pci_ops into one as PCI core code
> > > already provides separate callbacks via bridge->ops and bridge->child_ops.
> > >
> > > Signed-off-by: Pali Rohár <[email protected]>
> > > ---
> > > drivers/pci/controller/pci-mvebu.c | 82 ++++++++++++++++--------------
> > > 1 file changed, 44 insertions(+), 38 deletions(-)
> > >
> > > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > > index 9ea2f6a7c2b0..1e90ab888075 100644
> > > --- a/drivers/pci/controller/pci-mvebu.c
> > > +++ b/drivers/pci/controller/pci-mvebu.c
> > > @@ -294,11 +294,29 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > mvebu_writel(port, mask, PCIE_MASK_OFF);
> > > }
> > >
> > > -static int mvebu_pcie_hw_rd_conf(struct mvebu_pcie_port *port,
> > > - struct pci_bus *bus,
> > > - u32 devfn, int where, int size, u32 *val)
> > > +static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
> > > + struct pci_bus *bus,
> > > + int devfn);
> > > +
> > > +static int mvebu_pcie_child_rd_conf(struct pci_bus *bus, u32 devfn, int where,
> > > + int size, u32 *val)
> > > {
> > > - void __iomem *conf_data = port->base + PCIE_CONF_DATA_OFF;
> > > + struct mvebu_pcie *pcie = bus->sysdata;
> > > + struct mvebu_pcie_port *port;
> > > + void __iomem *conf_data;
> > > +
> > > + port = mvebu_pcie_find_port(pcie, bus, devfn);
> > > + if (!port) {
> > > + *val = 0xffffffff;
> > > + return PCIBIOS_DEVICE_NOT_FOUND;
> > > + }
> > > +
> > > + if (!mvebu_pcie_link_up(port)) {
> > > + *val = 0xffffffff;
> > > + return PCIBIOS_DEVICE_NOT_FOUND;
> > > + }
> > > +
> > > + conf_data = port->base + PCIE_CONF_DATA_OFF;
> > >
> > > mvebu_writel(port, PCIE_CONF_ADDR(bus->number, devfn, where),
> > > PCIE_CONF_ADDR_OFF);
> > > @@ -321,11 +339,21 @@ static int mvebu_pcie_hw_rd_conf(struct mvebu_pcie_port *port,
> > > return PCIBIOS_SUCCESSFUL;
> > > }
> > >
> > > -static int mvebu_pcie_hw_wr_conf(struct mvebu_pcie_port *port,
> > > - struct pci_bus *bus,
> > > - u32 devfn, int where, int size, u32 val)
> > > +static int mvebu_pcie_child_wr_conf(struct pci_bus *bus, u32 devfn,
> > > + int where, int size, u32 val)
> > > {
> > > - void __iomem *conf_data = port->base + PCIE_CONF_DATA_OFF;
> > > + struct mvebu_pcie *pcie = bus->sysdata;
> > > + struct mvebu_pcie_port *port;
> > > + void __iomem *conf_data;
> > > +
> >
> > > + port = mvebu_pcie_find_port(pcie, bus, devfn);
> > > + if (!port)
> > > + return PCIBIOS_DEVICE_NOT_FOUND;
> > > +
> > > + if (!mvebu_pcie_link_up(port))
> > > + return PCIBIOS_DEVICE_NOT_FOUND;
> > > +
> > > + conf_data = port->base + PCIE_CONF_DATA_OFF;
> >
> > Again, the same setup code in read and write is a sign to use
> > .map_bus(). You can copy it from my version I pointed you to.
> >
> > Rob
>
> I'm planning to do other cleanup in followup patches. But there are too
> many mvebu and aardvark patches on the list waiting, and I do not want
> to send another batch.
It can all be part of this patch.
Rob
On Friday 11 February 2022 17:19:17 Lorenzo Pieralisi wrote:
> On Wed, Jan 12, 2022 at 04:18:13PM +0100, Pali Rohár wrote:
> > This adds support for legacy INTx interrupts received from other PCIe
> > devices and which are reported by a new INTx irq chip.
> >
> > With this change, kernel can distinguish between INTA, INTB, INTC and INTD
> > interrupts.
> >
> > Note that for this support, device tree files has to be properly adjusted
> > to provide "interrupts" or "interrupts-extended" property with intx
> > interrupt source, "interrupt-names" property with "intx" string and also
> > 'interrupt-controller' subnode must be defined.
> >
> > If device tree files do not provide these nodes then driver would work as
> > before.
>
> Nit: this information is not useful. DT rules are written in DT
> bindings, not in kernel commit logs. All I am saying is that firmware
> developers should not have to read this log to write firmware.
It was not intended for firmware developers, but for reviewers of this
patch to understand, what is happening in code and that with old DT
files this patch does not change driver behavior (= work as before).
> > Signed-off-by: Pali Rohár <[email protected]>
> > ---
> > drivers/pci/controller/pci-mvebu.c | 185 +++++++++++++++++++++++++++--
> > 1 file changed, 177 insertions(+), 8 deletions(-)
> >
> > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > index 1e90ab888075..dbb6ecb4cb70 100644
> > --- a/drivers/pci/controller/pci-mvebu.c
> > +++ b/drivers/pci/controller/pci-mvebu.c
> > @@ -54,9 +54,10 @@
> > PCIE_CONF_ADDR_EN)
> > #define PCIE_CONF_DATA_OFF 0x18fc
> > #define PCIE_INT_CAUSE_OFF 0x1900
> > +#define PCIE_INT_UNMASK_OFF 0x1910
>
> Nit: I understand it is tempting but here you are redefining or better
> giving a proper label to a register. Separate patch please.
Ok!
> > +#define PCIE_INT_INTX(i) BIT(24+i)
> > #define PCIE_INT_PM_PME BIT(28)
> > -#define PCIE_MASK_OFF 0x1910
>
> See above.
>
> > -#define PCIE_MASK_ENABLE_INTS 0x0f000000
> > +#define PCIE_INT_ALL_MASK GENMASK(31, 0)
> > #define PCIE_CTRL_OFF 0x1a00
> > #define PCIE_CTRL_X1_MODE 0x0001
> > #define PCIE_CTRL_RC_MODE BIT(1)
> > @@ -110,6 +111,9 @@ struct mvebu_pcie_port {
> > struct mvebu_pcie_window iowin;
> > u32 saved_pcie_stat;
> > struct resource regs;
> > + struct irq_domain *intx_irq_domain;
> > + raw_spinlock_t irq_lock;
> > + int intx_irq;
> > };
> >
> > static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
> > @@ -235,7 +239,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
> >
> > static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > {
> > - u32 ctrl, lnkcap, cmd, dev_rev, mask;
> > + u32 ctrl, lnkcap, cmd, dev_rev, unmask;
> >
> > /* Setup PCIe controller to Root Complex mode. */
> > ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> > @@ -288,10 +292,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > /* Point PCIe unit MBUS decode windows to DRAM space. */
> > mvebu_pcie_setup_wins(port);
> >
> > - /* Enable interrupt lines A-D. */
> > - mask = mvebu_readl(port, PCIE_MASK_OFF);
> > - mask |= PCIE_MASK_ENABLE_INTS;
> > - mvebu_writel(port, mask, PCIE_MASK_OFF);
> > + /* Mask all interrupt sources. */
> > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > +
> > + /* Clear all interrupt causes. */
> > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > +
> > + if (port->intx_irq <= 0) {
> > + /*
> > + * When neither "summary" interrupt, nor "intx" interrupt was
> > + * specified in DT then unmask all legacy INTx interrupts as in
> > + * this case driver does not provide a way for masking and
> > + * unmasking of individual legacy INTx interrupts. In this case
> > + * all interrupts, including legacy INTx are reported via one
> > + * shared GIC source and therefore kernel cannot distinguish
> > + * which individual legacy INTx was triggered. These interrupts
> > + * are shared, so it should not cause any issue. Just
> > + * performance penalty as every PCIe interrupt handler needs to
> > + * be called when some interrupt is triggered.
> > + */
>
> This comment applies to current mainline right (ie it describes how
> current mainline handles INTx) ? IMO you should split it out in a
> separate patch.
This above comment describe what happens in if-branch when intx_irq is
not set (as written in comment "when intx interrupt was not specified in
DT"). You are right that this is also the behavior in the current
mainline.
I'm not sure if this comment can be split out as support for "intx"
interrupt is in this patch.
> I understand it is hard but a patch is a logical _change_, this
> comment is a change per se, it is a clarification on current
> behaviour.
Ok, I could try to split this comment into two patches, but part about
if-branch comment needs to stay in "this" patch.
> > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > + unmask |= PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
> > + PCIE_INT_INTX(2) | PCIE_INT_INTX(3);
> > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > + }
> > }
> >
> > static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
> > @@ -924,6 +948,108 @@ static struct pci_ops mvebu_pcie_ops = {
> > .write = mvebu_pcie_wr_conf,
> > };
> >
> > +static void mvebu_pcie_intx_irq_mask(struct irq_data *d)
> > +{
> > + struct mvebu_pcie_port *port = d->domain->host_data;
> > + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> > + unsigned long flags;
> > + u32 unmask;
> > +
> > + raw_spin_lock_irqsave(&port->irq_lock, flags);
> > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > + unmask &= ~PCIE_INT_INTX(hwirq);
> > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> > +}
> > +
> > +static void mvebu_pcie_intx_irq_unmask(struct irq_data *d)
> > +{
> > + struct mvebu_pcie_port *port = d->domain->host_data;
> > + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> > + unsigned long flags;
> > + u32 unmask;
> > +
> > + raw_spin_lock_irqsave(&port->irq_lock, flags);
> > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > + unmask |= PCIE_INT_INTX(hwirq);
> > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> > +}
> > +
> > +static struct irq_chip intx_irq_chip = {
> > + .name = "mvebu-INTx",
> > + .irq_mask = mvebu_pcie_intx_irq_mask,
> > + .irq_unmask = mvebu_pcie_intx_irq_unmask,
> > +};
> > +
> > +static int mvebu_pcie_intx_irq_map(struct irq_domain *h,
> > + unsigned int virq, irq_hw_number_t hwirq)
> > +{
> > + struct mvebu_pcie_port *port = h->host_data;
> > +
> > + irq_set_status_flags(virq, IRQ_LEVEL);
> > + irq_set_chip_and_handler(virq, &intx_irq_chip, handle_level_irq);
> > + irq_set_chip_data(virq, port);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct irq_domain_ops mvebu_pcie_intx_irq_domain_ops = {
> > + .map = mvebu_pcie_intx_irq_map,
> > + .xlate = irq_domain_xlate_onecell,
> > +};
> > +
> > +static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
> > +{
> > + struct device *dev = &port->pcie->pdev->dev;
> > + struct device_node *pcie_intc_node;
> > +
> > + raw_spin_lock_init(&port->irq_lock);
> > +
> > + pcie_intc_node = of_get_next_child(port->dn, NULL);
> > + if (!pcie_intc_node) {
> > + dev_err(dev, "No PCIe Intc node found for %s\n", port->name);
> > + return -ENODEV;
> > + }
> > +
> > + port->intx_irq_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX,
> > + &mvebu_pcie_intx_irq_domain_ops,
> > + port);
> > + of_node_put(pcie_intc_node);
> > + if (!port->intx_irq_domain) {
> > + dev_err(dev, "Failed to get INTx IRQ domain for %s\n", port->name);
> > + return -ENOMEM;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void mvebu_pcie_irq_handler(struct irq_desc *desc)
> > +{
> > + struct mvebu_pcie_port *port = irq_desc_get_handler_data(desc);
> > + struct irq_chip *chip = irq_desc_get_chip(desc);
> > + struct device *dev = &port->pcie->pdev->dev;
> > + u32 cause, unmask, status;
> > + int i;
> > +
> > + chained_irq_enter(chip, desc);
> > +
> > + cause = mvebu_readl(port, PCIE_INT_CAUSE_OFF);
> > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > + status = cause & unmask;
> > +
> > + /* Process legacy INTx interrupts */
> > + for (i = 0; i < PCI_NUM_INTX; i++) {
> > + if (!(status & PCIE_INT_INTX(i)))
> > + continue;
> > +
> > + if (generic_handle_domain_irq(port->intx_irq_domain, i) == -EINVAL)
> > + dev_err_ratelimited(dev, "unexpected INT%c IRQ\n", (char)i+'A');
> > + }
> > +
> > + chained_irq_exit(chip, desc);
> > +}
> > +
> > static int mvebu_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
> > {
> > /* Interrupt support on mvebu emulated bridges is not implemented yet */
> > @@ -1121,6 +1247,21 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
> > port->io_attr = -1;
> > }
> >
> > + /*
> > + * Old DT bindings do not contain "intx" interrupt
> > + * so do not fail probing driver when interrupt does not exist.
> > + */
> > + port->intx_irq = of_irq_get_byname(child, "intx");
> > + if (port->intx_irq == -EPROBE_DEFER) {
> > + ret = port->intx_irq;
> > + goto err;
> > + }
> > + if (port->intx_irq <= 0) {
> > + dev_warn(dev, "%s: legacy INTx interrupts cannot be masked individually, "
> > + "%pOF does not contain intx interrupt\n",
> > + port->name, child);
>
> Here you end up with a new warning on existing firmware. Is it
> legitimate ? I would remove the dev_warn().
I added this warning in v2 because Marc wanted it.
Should I (again) remove it in v3?
> Rob certainly has more insightful advice on this.
>
> Thanks,
> Lorenzo
>
> > + }
> > +
> > reset_gpio = of_get_named_gpio_flags(child, "reset-gpios", 0, &flags);
> > if (reset_gpio == -EPROBE_DEFER) {
> > ret = reset_gpio;
> > @@ -1317,6 +1458,7 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
> >
> > for (i = 0; i < pcie->nports; i++) {
> > struct mvebu_pcie_port *port = &pcie->ports[i];
> > + int irq = port->intx_irq;
> >
> > child = port->dn;
> > if (!child)
> > @@ -1344,6 +1486,22 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
> > continue;
> > }
> >
> > + if (irq > 0) {
> > + ret = mvebu_pcie_init_irq_domain(port);
> > + if (ret) {
> > + dev_err(dev, "%s: cannot init irq domain\n",
> > + port->name);
> > + pci_bridge_emul_cleanup(&port->bridge);
> > + devm_iounmap(dev, port->base);
> > + port->base = NULL;
> > + mvebu_pcie_powerdown(port);
> > + continue;
> > + }
> > + irq_set_chained_handler_and_data(irq,
> > + mvebu_pcie_irq_handler,
> > + port);
> > + }
> > +
> > /*
> > * PCIe topology exported by mvebu hw is quite complicated. In
> > * reality has something like N fully independent host bridges
> > @@ -1448,6 +1606,7 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
> >
> > for (i = 0; i < pcie->nports; i++) {
> > struct mvebu_pcie_port *port = &pcie->ports[i];
> > + int irq = port->intx_irq;
> >
> > if (!port->base)
> > continue;
> > @@ -1458,7 +1617,17 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
> > mvebu_writel(port, cmd, PCIE_CMD_OFF);
> >
> > /* Mask all interrupt sources. */
> > - mvebu_writel(port, 0, PCIE_MASK_OFF);
> > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > +
> > + /* Clear all interrupt causes. */
> > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > +
> > + if (irq > 0)
> > + irq_set_chained_handler_and_data(irq, NULL, NULL);
> > +
> > + /* Remove IRQ domains. */
> > + if (port->intx_irq_domain)
> > + irq_domain_remove(port->intx_irq_domain);
> >
> > /* Free config space for emulated root bridge. */
> > pci_bridge_emul_cleanup(&port->bridge);
> > --
> > 2.20.1
> >
On Friday 11 February 2022 17:50:45 Lorenzo Pieralisi wrote:
> Do not repost yet, I will be back at v5.17-rc5 and work towards merging it for v5.18.
Ok!
On Fri, 11 Feb 2022 18:21:37 +0000,
Lorenzo Pieralisi <[email protected]> wrote:
>
> On Fri, Feb 11, 2022 at 06:52:02PM +0100, Pali Rohár wrote:
>
> [...]
>
> > > > @@ -1121,6 +1247,21 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
> > > > port->io_attr = -1;
> > > > }
> > > >
> > > > + /*
> > > > + * Old DT bindings do not contain "intx" interrupt
> > > > + * so do not fail probing driver when interrupt does not exist.
> > > > + */
> > > > + port->intx_irq = of_irq_get_byname(child, "intx");
> > > > + if (port->intx_irq == -EPROBE_DEFER) {
> > > > + ret = port->intx_irq;
> > > > + goto err;
> > > > + }
> > > > + if (port->intx_irq <= 0) {
> > > > + dev_warn(dev, "%s: legacy INTx interrupts cannot be masked individually, "
> > > > + "%pOF does not contain intx interrupt\n",
> > > > + port->name, child);
> > >
> > > Here you end up with a new warning on existing firmware. Is it
> > > legitimate ? I would remove the dev_warn().
> >
> > I added this warning in v2 because Marc wanted it.
> >
> > Should I (again) remove it in v3?
>
> No, I asked a question and gave an opinion, I appreciate Marc's concern
> so leave it (ie not everyone running a new kernel with new warnings on
> existing firmware would be happy - maybe it is a good way of forcing a
> firmware upgrade, you will tell me).
My concern is that short of being able to mask these interrupts, it is
possible for a device to assert an interrupt that no driver handles,
and the kernel spurious interrupt detector won't be able to shut it
up. At this stage, the machine is totally dead (screaming *level*
interrupt).
The dev_warn() could toned down to a dev_warn_once() though.
M.
--
Without deviation from the norm, progress is not possible.
On Wed, Jan 12, 2022 at 04:18:13PM +0100, Pali Roh?r wrote:
> This adds support for legacy INTx interrupts received from other PCIe
> devices and which are reported by a new INTx irq chip.
>
> With this change, kernel can distinguish between INTA, INTB, INTC and INTD
> interrupts.
>
> Note that for this support, device tree files has to be properly adjusted
> to provide "interrupts" or "interrupts-extended" property with intx
> interrupt source, "interrupt-names" property with "intx" string and also
> 'interrupt-controller' subnode must be defined.
>
> If device tree files do not provide these nodes then driver would work as
> before.
Nit: this information is not useful. DT rules are written in DT
bindings, not in kernel commit logs. All I am saying is that firmware
developers should not have to read this log to write firmware.
> Signed-off-by: Pali Roh?r <[email protected]>
> ---
> drivers/pci/controller/pci-mvebu.c | 185 +++++++++++++++++++++++++++--
> 1 file changed, 177 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> index 1e90ab888075..dbb6ecb4cb70 100644
> --- a/drivers/pci/controller/pci-mvebu.c
> +++ b/drivers/pci/controller/pci-mvebu.c
> @@ -54,9 +54,10 @@
> PCIE_CONF_ADDR_EN)
> #define PCIE_CONF_DATA_OFF 0x18fc
> #define PCIE_INT_CAUSE_OFF 0x1900
> +#define PCIE_INT_UNMASK_OFF 0x1910
Nit: I understand it is tempting but here you are redefining or better
giving a proper label to a register. Separate patch please.
> +#define PCIE_INT_INTX(i) BIT(24+i)
> #define PCIE_INT_PM_PME BIT(28)
> -#define PCIE_MASK_OFF 0x1910
See above.
> -#define PCIE_MASK_ENABLE_INTS 0x0f000000
> +#define PCIE_INT_ALL_MASK GENMASK(31, 0)
> #define PCIE_CTRL_OFF 0x1a00
> #define PCIE_CTRL_X1_MODE 0x0001
> #define PCIE_CTRL_RC_MODE BIT(1)
> @@ -110,6 +111,9 @@ struct mvebu_pcie_port {
> struct mvebu_pcie_window iowin;
> u32 saved_pcie_stat;
> struct resource regs;
> + struct irq_domain *intx_irq_domain;
> + raw_spinlock_t irq_lock;
> + int intx_irq;
> };
>
> static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
> @@ -235,7 +239,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
>
> static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> {
> - u32 ctrl, lnkcap, cmd, dev_rev, mask;
> + u32 ctrl, lnkcap, cmd, dev_rev, unmask;
>
> /* Setup PCIe controller to Root Complex mode. */
> ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> @@ -288,10 +292,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> /* Point PCIe unit MBUS decode windows to DRAM space. */
> mvebu_pcie_setup_wins(port);
>
> - /* Enable interrupt lines A-D. */
> - mask = mvebu_readl(port, PCIE_MASK_OFF);
> - mask |= PCIE_MASK_ENABLE_INTS;
> - mvebu_writel(port, mask, PCIE_MASK_OFF);
> + /* Mask all interrupt sources. */
> + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> +
> + /* Clear all interrupt causes. */
> + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> +
> + if (port->intx_irq <= 0) {
> + /*
> + * When neither "summary" interrupt, nor "intx" interrupt was
> + * specified in DT then unmask all legacy INTx interrupts as in
> + * this case driver does not provide a way for masking and
> + * unmasking of individual legacy INTx interrupts. In this case
> + * all interrupts, including legacy INTx are reported via one
> + * shared GIC source and therefore kernel cannot distinguish
> + * which individual legacy INTx was triggered. These interrupts
> + * are shared, so it should not cause any issue. Just
> + * performance penalty as every PCIe interrupt handler needs to
> + * be called when some interrupt is triggered.
> + */
This comment applies to current mainline right (ie it describes how
current mainline handles INTx) ? IMO you should split it out in a
separate patch.
I understand it is hard but a patch is a logical _change_, this
comment is a change per se, it is a clarification on current
behaviour.
> + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> + unmask |= PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
> + PCIE_INT_INTX(2) | PCIE_INT_INTX(3);
> + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> + }
> }
>
> static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
> @@ -924,6 +948,108 @@ static struct pci_ops mvebu_pcie_ops = {
> .write = mvebu_pcie_wr_conf,
> };
>
> +static void mvebu_pcie_intx_irq_mask(struct irq_data *d)
> +{
> + struct mvebu_pcie_port *port = d->domain->host_data;
> + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> + unsigned long flags;
> + u32 unmask;
> +
> + raw_spin_lock_irqsave(&port->irq_lock, flags);
> + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> + unmask &= ~PCIE_INT_INTX(hwirq);
> + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> +}
> +
> +static void mvebu_pcie_intx_irq_unmask(struct irq_data *d)
> +{
> + struct mvebu_pcie_port *port = d->domain->host_data;
> + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> + unsigned long flags;
> + u32 unmask;
> +
> + raw_spin_lock_irqsave(&port->irq_lock, flags);
> + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> + unmask |= PCIE_INT_INTX(hwirq);
> + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> +}
> +
> +static struct irq_chip intx_irq_chip = {
> + .name = "mvebu-INTx",
> + .irq_mask = mvebu_pcie_intx_irq_mask,
> + .irq_unmask = mvebu_pcie_intx_irq_unmask,
> +};
> +
> +static int mvebu_pcie_intx_irq_map(struct irq_domain *h,
> + unsigned int virq, irq_hw_number_t hwirq)
> +{
> + struct mvebu_pcie_port *port = h->host_data;
> +
> + irq_set_status_flags(virq, IRQ_LEVEL);
> + irq_set_chip_and_handler(virq, &intx_irq_chip, handle_level_irq);
> + irq_set_chip_data(virq, port);
> +
> + return 0;
> +}
> +
> +static const struct irq_domain_ops mvebu_pcie_intx_irq_domain_ops = {
> + .map = mvebu_pcie_intx_irq_map,
> + .xlate = irq_domain_xlate_onecell,
> +};
> +
> +static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
> +{
> + struct device *dev = &port->pcie->pdev->dev;
> + struct device_node *pcie_intc_node;
> +
> + raw_spin_lock_init(&port->irq_lock);
> +
> + pcie_intc_node = of_get_next_child(port->dn, NULL);
> + if (!pcie_intc_node) {
> + dev_err(dev, "No PCIe Intc node found for %s\n", port->name);
> + return -ENODEV;
> + }
> +
> + port->intx_irq_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX,
> + &mvebu_pcie_intx_irq_domain_ops,
> + port);
> + of_node_put(pcie_intc_node);
> + if (!port->intx_irq_domain) {
> + dev_err(dev, "Failed to get INTx IRQ domain for %s\n", port->name);
> + return -ENOMEM;
> + }
> +
> + return 0;
> +}
> +
> +static void mvebu_pcie_irq_handler(struct irq_desc *desc)
> +{
> + struct mvebu_pcie_port *port = irq_desc_get_handler_data(desc);
> + struct irq_chip *chip = irq_desc_get_chip(desc);
> + struct device *dev = &port->pcie->pdev->dev;
> + u32 cause, unmask, status;
> + int i;
> +
> + chained_irq_enter(chip, desc);
> +
> + cause = mvebu_readl(port, PCIE_INT_CAUSE_OFF);
> + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> + status = cause & unmask;
> +
> + /* Process legacy INTx interrupts */
> + for (i = 0; i < PCI_NUM_INTX; i++) {
> + if (!(status & PCIE_INT_INTX(i)))
> + continue;
> +
> + if (generic_handle_domain_irq(port->intx_irq_domain, i) == -EINVAL)
> + dev_err_ratelimited(dev, "unexpected INT%c IRQ\n", (char)i+'A');
> + }
> +
> + chained_irq_exit(chip, desc);
> +}
> +
> static int mvebu_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
> {
> /* Interrupt support on mvebu emulated bridges is not implemented yet */
> @@ -1121,6 +1247,21 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
> port->io_attr = -1;
> }
>
> + /*
> + * Old DT bindings do not contain "intx" interrupt
> + * so do not fail probing driver when interrupt does not exist.
> + */
> + port->intx_irq = of_irq_get_byname(child, "intx");
> + if (port->intx_irq == -EPROBE_DEFER) {
> + ret = port->intx_irq;
> + goto err;
> + }
> + if (port->intx_irq <= 0) {
> + dev_warn(dev, "%s: legacy INTx interrupts cannot be masked individually, "
> + "%pOF does not contain intx interrupt\n",
> + port->name, child);
Here you end up with a new warning on existing firmware. Is it
legitimate ? I would remove the dev_warn().
Rob certainly has more insightful advice on this.
Thanks,
Lorenzo
> + }
> +
> reset_gpio = of_get_named_gpio_flags(child, "reset-gpios", 0, &flags);
> if (reset_gpio == -EPROBE_DEFER) {
> ret = reset_gpio;
> @@ -1317,6 +1458,7 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
>
> for (i = 0; i < pcie->nports; i++) {
> struct mvebu_pcie_port *port = &pcie->ports[i];
> + int irq = port->intx_irq;
>
> child = port->dn;
> if (!child)
> @@ -1344,6 +1486,22 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
> continue;
> }
>
> + if (irq > 0) {
> + ret = mvebu_pcie_init_irq_domain(port);
> + if (ret) {
> + dev_err(dev, "%s: cannot init irq domain\n",
> + port->name);
> + pci_bridge_emul_cleanup(&port->bridge);
> + devm_iounmap(dev, port->base);
> + port->base = NULL;
> + mvebu_pcie_powerdown(port);
> + continue;
> + }
> + irq_set_chained_handler_and_data(irq,
> + mvebu_pcie_irq_handler,
> + port);
> + }
> +
> /*
> * PCIe topology exported by mvebu hw is quite complicated. In
> * reality has something like N fully independent host bridges
> @@ -1448,6 +1606,7 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
>
> for (i = 0; i < pcie->nports; i++) {
> struct mvebu_pcie_port *port = &pcie->ports[i];
> + int irq = port->intx_irq;
>
> if (!port->base)
> continue;
> @@ -1458,7 +1617,17 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
> mvebu_writel(port, cmd, PCIE_CMD_OFF);
>
> /* Mask all interrupt sources. */
> - mvebu_writel(port, 0, PCIE_MASK_OFF);
> + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> +
> + /* Clear all interrupt causes. */
> + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> +
> + if (irq > 0)
> + irq_set_chained_handler_and_data(irq, NULL, NULL);
> +
> + /* Remove IRQ domains. */
> + if (port->intx_irq_domain)
> + irq_domain_remove(port->intx_irq_domain);
>
> /* Free config space for emulated root bridge. */
> pci_bridge_emul_cleanup(&port->bridge);
> --
> 2.20.1
>
On Wed, Jan 05, 2022 at 04:02:28PM +0100, Pali Roh?r wrote:
> This patch series extends pci-bridge-emul.c driver to emulate PCI Subsystem
> Vendor ID capability and PCIe extended capabilities. And then implement
> in pci-mvebu.c driver support for PCI Subsystem Vendor IDs, PCIe AER
> registers, support for legacy INTx interrupts, configuration for X1/X4
> mode and usage of new PCI child_ops API.
>
> This patch series depends on other pci-mvebu and pci-bridge-emul patches from:
> https://lore.kernel.org/linux-pci/[email protected]/
Hi Pali,
I went through the series quickly and I don't think there is anything
controversial, posted some minor comments. Do not repost yet, I will
be back at v5.17-rc5 and work towards merging it for v5.18.
Lorenzo
>
> Pali Roh?r (9):
> PCI: pci-bridge-emul: Add support for PCI Bridge Subsystem Vendor ID
> capability
> dt-bindings: PCI: mvebu: Add num-lanes property
> PCI: mvebu: Correctly configure x1/x4 mode
> PCI: mvebu: Add support for PCI Bridge Subsystem Vendor ID on emulated
> bridge
> PCI: mvebu: Add support for Advanced Error Reporting registers on
> emulated bridge
> PCI: mvebu: Use child_ops API
> dt-bindings: PCI: mvebu: Update information about intx interrupts
> PCI: mvebu: Implement support for legacy INTx interrupts
> ARM: dts: armada-385.dtsi: Add definitions for PCIe legacy INTx
> interrupts
>
> Russell King (2):
> PCI: pci-bridge-emul: Re-arrange register tests
> PCI: pci-bridge-emul: Add support for PCIe extended capabilities
>
> .../devicetree/bindings/pci/mvebu-pci.txt | 16 +
> arch/arm/boot/dts/armada-385.dtsi | 52 ++-
> drivers/pci/controller/pci-mvebu.c | 352 +++++++++++++++---
> drivers/pci/pci-bridge-emul.c | 167 ++++++---
> drivers/pci/pci-bridge-emul.h | 17 +
> 5 files changed, 494 insertions(+), 110 deletions(-)
>
> --
> 2.20.1
>
On Monday 14 February 2022 16:07:13 Gregory CLEMENT wrote:
> Hello Pali,
>
> > With this change legacy INTA, INTB, INTC and INTD interrupts are reported
> > separately and not mixed into one Linux virq source anymore.
> >
> > Signed-off-by: Pali Rohár <[email protected]>
> > ---
> > arch/arm/boot/dts/armada-385.dtsi | 52 ++++++++++++++++++++++++++-----
>
> Is there any reason for not doing the same change in armada-380.dtsi ?
I do not have A380 HW, so I did this change only for A385 which I have tested.
> Grégory
>
> > 1 file changed, 44 insertions(+), 8 deletions(-)
> >
> > diff --git a/arch/arm/boot/dts/armada-385.dtsi b/arch/arm/boot/dts/armada-385.dtsi
> > index f0022d10c715..83392b92dae2 100644
> > --- a/arch/arm/boot/dts/armada-385.dtsi
> > +++ b/arch/arm/boot/dts/armada-385.dtsi
> > @@ -69,16 +69,25 @@
> > reg = <0x0800 0 0 0 0>;
> > #address-cells = <3>;
> > #size-cells = <2>;
> > + interrupt-names = "intx";
> > + interrupts-extended = <&gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
> > #interrupt-cells = <1>;
> > ranges = <0x82000000 0 0 0x82000000 0x1 0 1 0
> > 0x81000000 0 0 0x81000000 0x1 0 1 0>;
> > bus-range = <0x00 0xff>;
> > - interrupt-map-mask = <0 0 0 0>;
> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
> > + interrupt-map-mask = <0 0 0 7>;
> > + interrupt-map = <0 0 0 1 &pcie1_intc 0>,
> > + <0 0 0 2 &pcie1_intc 1>,
> > + <0 0 0 3 &pcie1_intc 2>,
> > + <0 0 0 4 &pcie1_intc 3>;
> > marvell,pcie-port = <0>;
> > marvell,pcie-lane = <0>;
> > clocks = <&gateclk 8>;
> > status = "disabled";
> > + pcie1_intc: interrupt-controller {
> > + interrupt-controller;
> > + #interrupt-cells = <1>;
> > + };
> > };
> >
> > /* x1 port */
> > @@ -88,16 +97,25 @@
> > reg = <0x1000 0 0 0 0>;
> > #address-cells = <3>;
> > #size-cells = <2>;
> > + interrupt-names = "intx";
> > + interrupts-extended = <&gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
> > #interrupt-cells = <1>;
> > ranges = <0x82000000 0 0 0x82000000 0x2 0 1 0
> > 0x81000000 0 0 0x81000000 0x2 0 1 0>;
> > bus-range = <0x00 0xff>;
> > - interrupt-map-mask = <0 0 0 0>;
> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
> > + interrupt-map-mask = <0 0 0 7>;
> > + interrupt-map = <0 0 0 1 &pcie2_intc 0>,
> > + <0 0 0 2 &pcie2_intc 1>,
> > + <0 0 0 3 &pcie2_intc 2>,
> > + <0 0 0 4 &pcie2_intc 3>;
> > marvell,pcie-port = <1>;
> > marvell,pcie-lane = <0>;
> > clocks = <&gateclk 5>;
> > status = "disabled";
> > + pcie2_intc: interrupt-controller {
> > + interrupt-controller;
> > + #interrupt-cells = <1>;
> > + };
> > };
> >
> > /* x1 port */
> > @@ -107,16 +125,25 @@
> > reg = <0x1800 0 0 0 0>;
> > #address-cells = <3>;
> > #size-cells = <2>;
> > + interrupt-names = "intx";
> > + interrupts-extended = <&gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
> > #interrupt-cells = <1>;
> > ranges = <0x82000000 0 0 0x82000000 0x3 0 1 0
> > 0x81000000 0 0 0x81000000 0x3 0 1 0>;
> > bus-range = <0x00 0xff>;
> > - interrupt-map-mask = <0 0 0 0>;
> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
> > + interrupt-map-mask = <0 0 0 7>;
> > + interrupt-map = <0 0 0 1 &pcie3_intc 0>,
> > + <0 0 0 2 &pcie3_intc 1>,
> > + <0 0 0 3 &pcie3_intc 2>,
> > + <0 0 0 4 &pcie3_intc 3>;
> > marvell,pcie-port = <2>;
> > marvell,pcie-lane = <0>;
> > clocks = <&gateclk 6>;
> > status = "disabled";
> > + pcie3_intc: interrupt-controller {
> > + interrupt-controller;
> > + #interrupt-cells = <1>;
> > + };
> > };
> >
> > /*
> > @@ -129,16 +156,25 @@
> > reg = <0x2000 0 0 0 0>;
> > #address-cells = <3>;
> > #size-cells = <2>;
> > + interrupt-names = "intx";
> > + interrupts-extended = <&gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
> > #interrupt-cells = <1>;
> > ranges = <0x82000000 0 0 0x82000000 0x4 0 1 0
> > 0x81000000 0 0 0x81000000 0x4 0 1 0>;
> > bus-range = <0x00 0xff>;
> > - interrupt-map-mask = <0 0 0 0>;
> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
> > + interrupt-map-mask = <0 0 0 7>;
> > + interrupt-map = <0 0 0 1 &pcie4_intc 0>,
> > + <0 0 0 2 &pcie4_intc 1>,
> > + <0 0 0 3 &pcie4_intc 2>,
> > + <0 0 0 4 &pcie4_intc 3>;
> > marvell,pcie-port = <3>;
> > marvell,pcie-lane = <0>;
> > clocks = <&gateclk 7>;
> > status = "disabled";
> > + pcie4_intc: interrupt-controller {
> > + interrupt-controller;
> > + #interrupt-cells = <1>;
> > + };
> > };
> > };
> > };
> > --
> > 2.20.1
> >
>
> --
> Gregory Clement, Bootlin
> Embedded Linux and Kernel engineering
> http://bootlin.com
Hello,
> On Monday 14 February 2022 16:07:13 Gregory CLEMENT wrote:
>> Hello Pali,
>>
>> > With this change legacy INTA, INTB, INTC and INTD interrupts are reported
>> > separately and not mixed into one Linux virq source anymore.
>> >
>> > Signed-off-by: Pali Rohár <[email protected]>
>> > ---
>> > arch/arm/boot/dts/armada-385.dtsi | 52 ++++++++++++++++++++++++++-----
>>
>> Is there any reason for not doing the same change in armada-380.dtsi ?
>
> I do not have A380 HW, so I did this change only for A385 which I have
> tested.
OK fair enough.
So you can add my
Acked-by: Gregory CLEMENT <[email protected]>
Moreover to keep biscetability this patch should be merged after the
support in the driver. So the easier is to let merge it through the PCI
subsystem with the other patches from this series. I do not think there
will be any other changes in this file so there won't be any merge
conflicts.
Thanks,
Grégory
>
>> Grégory
>>
>> > 1 file changed, 44 insertions(+), 8 deletions(-)
>> >
>> > diff --git a/arch/arm/boot/dts/armada-385.dtsi b/arch/arm/boot/dts/armada-385.dtsi
>> > index f0022d10c715..83392b92dae2 100644
>> > --- a/arch/arm/boot/dts/armada-385.dtsi
>> > +++ b/arch/arm/boot/dts/armada-385.dtsi
>> > @@ -69,16 +69,25 @@
>> > reg = <0x0800 0 0 0 0>;
>> > #address-cells = <3>;
>> > #size-cells = <2>;
>> > + interrupt-names = "intx";
>> > + interrupts-extended = <&gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
>> > #interrupt-cells = <1>;
>> > ranges = <0x82000000 0 0 0x82000000 0x1 0 1 0
>> > 0x81000000 0 0 0x81000000 0x1 0 1 0>;
>> > bus-range = <0x00 0xff>;
>> > - interrupt-map-mask = <0 0 0 0>;
>> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
>> > + interrupt-map-mask = <0 0 0 7>;
>> > + interrupt-map = <0 0 0 1 &pcie1_intc 0>,
>> > + <0 0 0 2 &pcie1_intc 1>,
>> > + <0 0 0 3 &pcie1_intc 2>,
>> > + <0 0 0 4 &pcie1_intc 3>;
>> > marvell,pcie-port = <0>;
>> > marvell,pcie-lane = <0>;
>> > clocks = <&gateclk 8>;
>> > status = "disabled";
>> > + pcie1_intc: interrupt-controller {
>> > + interrupt-controller;
>> > + #interrupt-cells = <1>;
>> > + };
>> > };
>> >
>> > /* x1 port */
>> > @@ -88,16 +97,25 @@
>> > reg = <0x1000 0 0 0 0>;
>> > #address-cells = <3>;
>> > #size-cells = <2>;
>> > + interrupt-names = "intx";
>> > + interrupts-extended = <&gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
>> > #interrupt-cells = <1>;
>> > ranges = <0x82000000 0 0 0x82000000 0x2 0 1 0
>> > 0x81000000 0 0 0x81000000 0x2 0 1 0>;
>> > bus-range = <0x00 0xff>;
>> > - interrupt-map-mask = <0 0 0 0>;
>> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
>> > + interrupt-map-mask = <0 0 0 7>;
>> > + interrupt-map = <0 0 0 1 &pcie2_intc 0>,
>> > + <0 0 0 2 &pcie2_intc 1>,
>> > + <0 0 0 3 &pcie2_intc 2>,
>> > + <0 0 0 4 &pcie2_intc 3>;
>> > marvell,pcie-port = <1>;
>> > marvell,pcie-lane = <0>;
>> > clocks = <&gateclk 5>;
>> > status = "disabled";
>> > + pcie2_intc: interrupt-controller {
>> > + interrupt-controller;
>> > + #interrupt-cells = <1>;
>> > + };
>> > };
>> >
>> > /* x1 port */
>> > @@ -107,16 +125,25 @@
>> > reg = <0x1800 0 0 0 0>;
>> > #address-cells = <3>;
>> > #size-cells = <2>;
>> > + interrupt-names = "intx";
>> > + interrupts-extended = <&gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
>> > #interrupt-cells = <1>;
>> > ranges = <0x82000000 0 0 0x82000000 0x3 0 1 0
>> > 0x81000000 0 0 0x81000000 0x3 0 1 0>;
>> > bus-range = <0x00 0xff>;
>> > - interrupt-map-mask = <0 0 0 0>;
>> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
>> > + interrupt-map-mask = <0 0 0 7>;
>> > + interrupt-map = <0 0 0 1 &pcie3_intc 0>,
>> > + <0 0 0 2 &pcie3_intc 1>,
>> > + <0 0 0 3 &pcie3_intc 2>,
>> > + <0 0 0 4 &pcie3_intc 3>;
>> > marvell,pcie-port = <2>;
>> > marvell,pcie-lane = <0>;
>> > clocks = <&gateclk 6>;
>> > status = "disabled";
>> > + pcie3_intc: interrupt-controller {
>> > + interrupt-controller;
>> > + #interrupt-cells = <1>;
>> > + };
>> > };
>> >
>> > /*
>> > @@ -129,16 +156,25 @@
>> > reg = <0x2000 0 0 0 0>;
>> > #address-cells = <3>;
>> > #size-cells = <2>;
>> > + interrupt-names = "intx";
>> > + interrupts-extended = <&gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
>> > #interrupt-cells = <1>;
>> > ranges = <0x82000000 0 0 0x82000000 0x4 0 1 0
>> > 0x81000000 0 0 0x81000000 0x4 0 1 0>;
>> > bus-range = <0x00 0xff>;
>> > - interrupt-map-mask = <0 0 0 0>;
>> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
>> > + interrupt-map-mask = <0 0 0 7>;
>> > + interrupt-map = <0 0 0 1 &pcie4_intc 0>,
>> > + <0 0 0 2 &pcie4_intc 1>,
>> > + <0 0 0 3 &pcie4_intc 2>,
>> > + <0 0 0 4 &pcie4_intc 3>;
>> > marvell,pcie-port = <3>;
>> > marvell,pcie-lane = <0>;
>> > clocks = <&gateclk 7>;
>> > status = "disabled";
>> > + pcie4_intc: interrupt-controller {
>> > + interrupt-controller;
>> > + #interrupt-cells = <1>;
>> > + };
>> > };
>> > };
>> > };
>> > --
>> > 2.20.1
>> >
>>
>> --
>> Gregory Clement, Bootlin
>> Embedded Linux and Kernel engineering
>> http://bootlin.com
--
Gregory Clement, Bootlin
Embedded Linux and Kernel engineering
http://bootlin.com
On Fri, Feb 11, 2022 at 06:52:02PM +0100, Pali Roh?r wrote:
[...]
> > > @@ -1121,6 +1247,21 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
> > > port->io_attr = -1;
> > > }
> > >
> > > + /*
> > > + * Old DT bindings do not contain "intx" interrupt
> > > + * so do not fail probing driver when interrupt does not exist.
> > > + */
> > > + port->intx_irq = of_irq_get_byname(child, "intx");
> > > + if (port->intx_irq == -EPROBE_DEFER) {
> > > + ret = port->intx_irq;
> > > + goto err;
> > > + }
> > > + if (port->intx_irq <= 0) {
> > > + dev_warn(dev, "%s: legacy INTx interrupts cannot be masked individually, "
> > > + "%pOF does not contain intx interrupt\n",
> > > + port->name, child);
> >
> > Here you end up with a new warning on existing firmware. Is it
> > legitimate ? I would remove the dev_warn().
>
> I added this warning in v2 because Marc wanted it.
>
> Should I (again) remove it in v3?
No, I asked a question and gave an opinion, I appreciate Marc's concern
so leave it (ie not everyone running a new kernel with new warnings on
existing firmware would be happy - maybe it is a good way of forcing a
firmware upgrade, you will tell me).
Lorenzo
> > Rob certainly has more insightful advice on this.
> >
> > Thanks,
> > Lorenzo
> >
> > > + }
> > > +
> > > reset_gpio = of_get_named_gpio_flags(child, "reset-gpios", 0, &flags);
> > > if (reset_gpio == -EPROBE_DEFER) {
> > > ret = reset_gpio;
> > > @@ -1317,6 +1458,7 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
> > >
> > > for (i = 0; i < pcie->nports; i++) {
> > > struct mvebu_pcie_port *port = &pcie->ports[i];
> > > + int irq = port->intx_irq;
> > >
> > > child = port->dn;
> > > if (!child)
> > > @@ -1344,6 +1486,22 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
> > > continue;
> > > }
> > >
> > > + if (irq > 0) {
> > > + ret = mvebu_pcie_init_irq_domain(port);
> > > + if (ret) {
> > > + dev_err(dev, "%s: cannot init irq domain\n",
> > > + port->name);
> > > + pci_bridge_emul_cleanup(&port->bridge);
> > > + devm_iounmap(dev, port->base);
> > > + port->base = NULL;
> > > + mvebu_pcie_powerdown(port);
> > > + continue;
> > > + }
> > > + irq_set_chained_handler_and_data(irq,
> > > + mvebu_pcie_irq_handler,
> > > + port);
> > > + }
> > > +
> > > /*
> > > * PCIe topology exported by mvebu hw is quite complicated. In
> > > * reality has something like N fully independent host bridges
> > > @@ -1448,6 +1606,7 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
> > >
> > > for (i = 0; i < pcie->nports; i++) {
> > > struct mvebu_pcie_port *port = &pcie->ports[i];
> > > + int irq = port->intx_irq;
> > >
> > > if (!port->base)
> > > continue;
> > > @@ -1458,7 +1617,17 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
> > > mvebu_writel(port, cmd, PCIE_CMD_OFF);
> > >
> > > /* Mask all interrupt sources. */
> > > - mvebu_writel(port, 0, PCIE_MASK_OFF);
> > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > > +
> > > + /* Clear all interrupt causes. */
> > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > > +
> > > + if (irq > 0)
> > > + irq_set_chained_handler_and_data(irq, NULL, NULL);
> > > +
> > > + /* Remove IRQ domains. */
> > > + if (port->intx_irq_domain)
> > > + irq_domain_remove(port->intx_irq_domain);
> > >
> > > /* Free config space for emulated root bridge. */
> > > pci_bridge_emul_cleanup(&port->bridge);
> > > --
> > > 2.20.1
> > >
Hello Pali,
> With this change legacy INTA, INTB, INTC and INTD interrupts are reported
> separately and not mixed into one Linux virq source anymore.
>
> Signed-off-by: Pali Rohár <[email protected]>
> ---
> arch/arm/boot/dts/armada-385.dtsi | 52 ++++++++++++++++++++++++++-----
Is there any reason for not doing the same change in armada-380.dtsi ?
Grégory
> 1 file changed, 44 insertions(+), 8 deletions(-)
>
> diff --git a/arch/arm/boot/dts/armada-385.dtsi b/arch/arm/boot/dts/armada-385.dtsi
> index f0022d10c715..83392b92dae2 100644
> --- a/arch/arm/boot/dts/armada-385.dtsi
> +++ b/arch/arm/boot/dts/armada-385.dtsi
> @@ -69,16 +69,25 @@
> reg = <0x0800 0 0 0 0>;
> #address-cells = <3>;
> #size-cells = <2>;
> + interrupt-names = "intx";
> + interrupts-extended = <&gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
> #interrupt-cells = <1>;
> ranges = <0x82000000 0 0 0x82000000 0x1 0 1 0
> 0x81000000 0 0 0x81000000 0x1 0 1 0>;
> bus-range = <0x00 0xff>;
> - interrupt-map-mask = <0 0 0 0>;
> - interrupt-map = <0 0 0 0 &gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
> + interrupt-map-mask = <0 0 0 7>;
> + interrupt-map = <0 0 0 1 &pcie1_intc 0>,
> + <0 0 0 2 &pcie1_intc 1>,
> + <0 0 0 3 &pcie1_intc 2>,
> + <0 0 0 4 &pcie1_intc 3>;
> marvell,pcie-port = <0>;
> marvell,pcie-lane = <0>;
> clocks = <&gateclk 8>;
> status = "disabled";
> + pcie1_intc: interrupt-controller {
> + interrupt-controller;
> + #interrupt-cells = <1>;
> + };
> };
>
> /* x1 port */
> @@ -88,16 +97,25 @@
> reg = <0x1000 0 0 0 0>;
> #address-cells = <3>;
> #size-cells = <2>;
> + interrupt-names = "intx";
> + interrupts-extended = <&gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
> #interrupt-cells = <1>;
> ranges = <0x82000000 0 0 0x82000000 0x2 0 1 0
> 0x81000000 0 0 0x81000000 0x2 0 1 0>;
> bus-range = <0x00 0xff>;
> - interrupt-map-mask = <0 0 0 0>;
> - interrupt-map = <0 0 0 0 &gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
> + interrupt-map-mask = <0 0 0 7>;
> + interrupt-map = <0 0 0 1 &pcie2_intc 0>,
> + <0 0 0 2 &pcie2_intc 1>,
> + <0 0 0 3 &pcie2_intc 2>,
> + <0 0 0 4 &pcie2_intc 3>;
> marvell,pcie-port = <1>;
> marvell,pcie-lane = <0>;
> clocks = <&gateclk 5>;
> status = "disabled";
> + pcie2_intc: interrupt-controller {
> + interrupt-controller;
> + #interrupt-cells = <1>;
> + };
> };
>
> /* x1 port */
> @@ -107,16 +125,25 @@
> reg = <0x1800 0 0 0 0>;
> #address-cells = <3>;
> #size-cells = <2>;
> + interrupt-names = "intx";
> + interrupts-extended = <&gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
> #interrupt-cells = <1>;
> ranges = <0x82000000 0 0 0x82000000 0x3 0 1 0
> 0x81000000 0 0 0x81000000 0x3 0 1 0>;
> bus-range = <0x00 0xff>;
> - interrupt-map-mask = <0 0 0 0>;
> - interrupt-map = <0 0 0 0 &gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
> + interrupt-map-mask = <0 0 0 7>;
> + interrupt-map = <0 0 0 1 &pcie3_intc 0>,
> + <0 0 0 2 &pcie3_intc 1>,
> + <0 0 0 3 &pcie3_intc 2>,
> + <0 0 0 4 &pcie3_intc 3>;
> marvell,pcie-port = <2>;
> marvell,pcie-lane = <0>;
> clocks = <&gateclk 6>;
> status = "disabled";
> + pcie3_intc: interrupt-controller {
> + interrupt-controller;
> + #interrupt-cells = <1>;
> + };
> };
>
> /*
> @@ -129,16 +156,25 @@
> reg = <0x2000 0 0 0 0>;
> #address-cells = <3>;
> #size-cells = <2>;
> + interrupt-names = "intx";
> + interrupts-extended = <&gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
> #interrupt-cells = <1>;
> ranges = <0x82000000 0 0 0x82000000 0x4 0 1 0
> 0x81000000 0 0 0x81000000 0x4 0 1 0>;
> bus-range = <0x00 0xff>;
> - interrupt-map-mask = <0 0 0 0>;
> - interrupt-map = <0 0 0 0 &gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
> + interrupt-map-mask = <0 0 0 7>;
> + interrupt-map = <0 0 0 1 &pcie4_intc 0>,
> + <0 0 0 2 &pcie4_intc 1>,
> + <0 0 0 3 &pcie4_intc 2>,
> + <0 0 0 4 &pcie4_intc 3>;
> marvell,pcie-port = <3>;
> marvell,pcie-lane = <0>;
> clocks = <&gateclk 7>;
> status = "disabled";
> + pcie4_intc: interrupt-controller {
> + interrupt-controller;
> + #interrupt-cells = <1>;
> + };
> };
> };
> };
> --
> 2.20.1
>
--
Gregory Clement, Bootlin
Embedded Linux and Kernel engineering
http://bootlin.com
Hello,
Sorry for jumping in the conversation, but I read this thread and I
have an Armada A388 HW so I can test it, if desired.
Luís
On Tue, Feb 15, 2022 at 10:47 AM Luís Mendes <[email protected]> wrote:
>
> Hello,
>
> Sorry for jumping in the conversation, but I read this thread and I have an Armada A388 HW so I can test it, if desired.
>
> Luís
>
> On Mon, Feb 14, 2022 at 7:57 PM Gregory CLEMENT <[email protected]> wrote:
>>
>> Hello,
>>
>> > On Monday 14 February 2022 16:07:13 Gregory CLEMENT wrote:
>> >> Hello Pali,
>> >>
>> >> > With this change legacy INTA, INTB, INTC and INTD interrupts are reported
>> >> > separately and not mixed into one Linux virq source anymore.
>> >> >
>> >> > Signed-off-by: Pali Rohár <[email protected]>
>> >> > ---
>> >> > arch/arm/boot/dts/armada-385.dtsi | 52 ++++++++++++++++++++++++++-----
>> >>
>> >> Is there any reason for not doing the same change in armada-380.dtsi ?
>> >
>> > I do not have A380 HW, so I did this change only for A385 which I have
>> > tested.
>>
>> OK fair enough.
>>
>> So you can add my
>> Acked-by: Gregory CLEMENT <[email protected]>
>>
>> Moreover to keep biscetability this patch should be merged after the
>> support in the driver. So the easier is to let merge it through the PCI
>> subsystem with the other patches from this series. I do not think there
>> will be any other changes in this file so there won't be any merge
>> conflicts.
>>
>> Thanks,
>>
>> Grégory
>>
>>
>> >
>> >> Grégory
>> >>
>> >> > 1 file changed, 44 insertions(+), 8 deletions(-)
>> >> >
>> >> > diff --git a/arch/arm/boot/dts/armada-385.dtsi b/arch/arm/boot/dts/armada-385.dtsi
>> >> > index f0022d10c715..83392b92dae2 100644
>> >> > --- a/arch/arm/boot/dts/armada-385.dtsi
>> >> > +++ b/arch/arm/boot/dts/armada-385.dtsi
>> >> > @@ -69,16 +69,25 @@
>> >> > reg = <0x0800 0 0 0 0>;
>> >> > #address-cells = <3>;
>> >> > #size-cells = <2>;
>> >> > + interrupt-names = "intx";
>> >> > + interrupts-extended = <&gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
>> >> > #interrupt-cells = <1>;
>> >> > ranges = <0x82000000 0 0 0x82000000 0x1 0 1 0
>> >> > 0x81000000 0 0 0x81000000 0x1 0 1 0>;
>> >> > bus-range = <0x00 0xff>;
>> >> > - interrupt-map-mask = <0 0 0 0>;
>> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
>> >> > + interrupt-map-mask = <0 0 0 7>;
>> >> > + interrupt-map = <0 0 0 1 &pcie1_intc 0>,
>> >> > + <0 0 0 2 &pcie1_intc 1>,
>> >> > + <0 0 0 3 &pcie1_intc 2>,
>> >> > + <0 0 0 4 &pcie1_intc 3>;
>> >> > marvell,pcie-port = <0>;
>> >> > marvell,pcie-lane = <0>;
>> >> > clocks = <&gateclk 8>;
>> >> > status = "disabled";
>> >> > + pcie1_intc: interrupt-controller {
>> >> > + interrupt-controller;
>> >> > + #interrupt-cells = <1>;
>> >> > + };
>> >> > };
>> >> >
>> >> > /* x1 port */
>> >> > @@ -88,16 +97,25 @@
>> >> > reg = <0x1000 0 0 0 0>;
>> >> > #address-cells = <3>;
>> >> > #size-cells = <2>;
>> >> > + interrupt-names = "intx";
>> >> > + interrupts-extended = <&gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
>> >> > #interrupt-cells = <1>;
>> >> > ranges = <0x82000000 0 0 0x82000000 0x2 0 1 0
>> >> > 0x81000000 0 0 0x81000000 0x2 0 1 0>;
>> >> > bus-range = <0x00 0xff>;
>> >> > - interrupt-map-mask = <0 0 0 0>;
>> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
>> >> > + interrupt-map-mask = <0 0 0 7>;
>> >> > + interrupt-map = <0 0 0 1 &pcie2_intc 0>,
>> >> > + <0 0 0 2 &pcie2_intc 1>,
>> >> > + <0 0 0 3 &pcie2_intc 2>,
>> >> > + <0 0 0 4 &pcie2_intc 3>;
>> >> > marvell,pcie-port = <1>;
>> >> > marvell,pcie-lane = <0>;
>> >> > clocks = <&gateclk 5>;
>> >> > status = "disabled";
>> >> > + pcie2_intc: interrupt-controller {
>> >> > + interrupt-controller;
>> >> > + #interrupt-cells = <1>;
>> >> > + };
>> >> > };
>> >> >
>> >> > /* x1 port */
>> >> > @@ -107,16 +125,25 @@
>> >> > reg = <0x1800 0 0 0 0>;
>> >> > #address-cells = <3>;
>> >> > #size-cells = <2>;
>> >> > + interrupt-names = "intx";
>> >> > + interrupts-extended = <&gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
>> >> > #interrupt-cells = <1>;
>> >> > ranges = <0x82000000 0 0 0x82000000 0x3 0 1 0
>> >> > 0x81000000 0 0 0x81000000 0x3 0 1 0>;
>> >> > bus-range = <0x00 0xff>;
>> >> > - interrupt-map-mask = <0 0 0 0>;
>> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
>> >> > + interrupt-map-mask = <0 0 0 7>;
>> >> > + interrupt-map = <0 0 0 1 &pcie3_intc 0>,
>> >> > + <0 0 0 2 &pcie3_intc 1>,
>> >> > + <0 0 0 3 &pcie3_intc 2>,
>> >> > + <0 0 0 4 &pcie3_intc 3>;
>> >> > marvell,pcie-port = <2>;
>> >> > marvell,pcie-lane = <0>;
>> >> > clocks = <&gateclk 6>;
>> >> > status = "disabled";
>> >> > + pcie3_intc: interrupt-controller {
>> >> > + interrupt-controller;
>> >> > + #interrupt-cells = <1>;
>> >> > + };
>> >> > };
>> >> >
>> >> > /*
>> >> > @@ -129,16 +156,25 @@
>> >> > reg = <0x2000 0 0 0 0>;
>> >> > #address-cells = <3>;
>> >> > #size-cells = <2>;
>> >> > + interrupt-names = "intx";
>> >> > + interrupts-extended = <&gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
>> >> > #interrupt-cells = <1>;
>> >> > ranges = <0x82000000 0 0 0x82000000 0x4 0 1 0
>> >> > 0x81000000 0 0 0x81000000 0x4 0 1 0>;
>> >> > bus-range = <0x00 0xff>;
>> >> > - interrupt-map-mask = <0 0 0 0>;
>> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
>> >> > + interrupt-map-mask = <0 0 0 7>;
>> >> > + interrupt-map = <0 0 0 1 &pcie4_intc 0>,
>> >> > + <0 0 0 2 &pcie4_intc 1>,
>> >> > + <0 0 0 3 &pcie4_intc 2>,
>> >> > + <0 0 0 4 &pcie4_intc 3>;
>> >> > marvell,pcie-port = <3>;
>> >> > marvell,pcie-lane = <0>;
>> >> > clocks = <&gateclk 7>;
>> >> > status = "disabled";
>> >> > + pcie4_intc: interrupt-controller {
>> >> > + interrupt-controller;
>> >> > + #interrupt-cells = <1>;
>> >> > + };
>> >> > };
>> >> > };
>> >> > };
>> >> > --
>> >> > 2.20.1
>> >> >
>> >>
>> >> --
>> >> Gregory Clement, Bootlin
>> >> Embedded Linux and Kernel engineering
>> >> http://bootlin.com
>>
>> --
>> Gregory Clement, Bootlin
>> Embedded Linux and Kernel engineering
>> http://bootlin.com
Hello! armada-388.dtsi file has #include "armada-385.dtsi" line and
therefore is already covered by this my patch.
Gregory's question was about A380.
But if you want, you can test this patch series (which already covers
A388) on your A388 HW. It is still better to do tests on more HW.
On Tuesday 15 February 2022 10:48:17 Luís Mendes wrote:
> Hello,
>
> Sorry for jumping in the conversation, but I read this thread and I
> have an Armada A388 HW so I can test it, if desired.
>
> Luís
>
>
> On Tue, Feb 15, 2022 at 10:47 AM Luís Mendes <[email protected]> wrote:
> >
> > Hello,
> >
> > Sorry for jumping in the conversation, but I read this thread and I have an Armada A388 HW so I can test it, if desired.
> >
> > Luís
> >
> > On Mon, Feb 14, 2022 at 7:57 PM Gregory CLEMENT <[email protected]> wrote:
> >>
> >> Hello,
> >>
> >> > On Monday 14 February 2022 16:07:13 Gregory CLEMENT wrote:
> >> >> Hello Pali,
> >> >>
> >> >> > With this change legacy INTA, INTB, INTC and INTD interrupts are reported
> >> >> > separately and not mixed into one Linux virq source anymore.
> >> >> >
> >> >> > Signed-off-by: Pali Rohár <[email protected]>
> >> >> > ---
> >> >> > arch/arm/boot/dts/armada-385.dtsi | 52 ++++++++++++++++++++++++++-----
> >> >>
> >> >> Is there any reason for not doing the same change in armada-380.dtsi ?
> >> >
> >> > I do not have A380 HW, so I did this change only for A385 which I have
> >> > tested.
> >>
> >> OK fair enough.
> >>
> >> So you can add my
> >> Acked-by: Gregory CLEMENT <[email protected]>
> >>
> >> Moreover to keep biscetability this patch should be merged after the
> >> support in the driver. So the easier is to let merge it through the PCI
> >> subsystem with the other patches from this series. I do not think there
> >> will be any other changes in this file so there won't be any merge
> >> conflicts.
> >>
> >> Thanks,
> >>
> >> Grégory
> >>
> >>
> >> >
> >> >> Grégory
> >> >>
> >> >> > 1 file changed, 44 insertions(+), 8 deletions(-)
> >> >> >
> >> >> > diff --git a/arch/arm/boot/dts/armada-385.dtsi b/arch/arm/boot/dts/armada-385.dtsi
> >> >> > index f0022d10c715..83392b92dae2 100644
> >> >> > --- a/arch/arm/boot/dts/armada-385.dtsi
> >> >> > +++ b/arch/arm/boot/dts/armada-385.dtsi
> >> >> > @@ -69,16 +69,25 @@
> >> >> > reg = <0x0800 0 0 0 0>;
> >> >> > #address-cells = <3>;
> >> >> > #size-cells = <2>;
> >> >> > + interrupt-names = "intx";
> >> >> > + interrupts-extended = <&gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
> >> >> > #interrupt-cells = <1>;
> >> >> > ranges = <0x82000000 0 0 0x82000000 0x1 0 1 0
> >> >> > 0x81000000 0 0 0x81000000 0x1 0 1 0>;
> >> >> > bus-range = <0x00 0xff>;
> >> >> > - interrupt-map-mask = <0 0 0 0>;
> >> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
> >> >> > + interrupt-map-mask = <0 0 0 7>;
> >> >> > + interrupt-map = <0 0 0 1 &pcie1_intc 0>,
> >> >> > + <0 0 0 2 &pcie1_intc 1>,
> >> >> > + <0 0 0 3 &pcie1_intc 2>,
> >> >> > + <0 0 0 4 &pcie1_intc 3>;
> >> >> > marvell,pcie-port = <0>;
> >> >> > marvell,pcie-lane = <0>;
> >> >> > clocks = <&gateclk 8>;
> >> >> > status = "disabled";
> >> >> > + pcie1_intc: interrupt-controller {
> >> >> > + interrupt-controller;
> >> >> > + #interrupt-cells = <1>;
> >> >> > + };
> >> >> > };
> >> >> >
> >> >> > /* x1 port */
> >> >> > @@ -88,16 +97,25 @@
> >> >> > reg = <0x1000 0 0 0 0>;
> >> >> > #address-cells = <3>;
> >> >> > #size-cells = <2>;
> >> >> > + interrupt-names = "intx";
> >> >> > + interrupts-extended = <&gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
> >> >> > #interrupt-cells = <1>;
> >> >> > ranges = <0x82000000 0 0 0x82000000 0x2 0 1 0
> >> >> > 0x81000000 0 0 0x81000000 0x2 0 1 0>;
> >> >> > bus-range = <0x00 0xff>;
> >> >> > - interrupt-map-mask = <0 0 0 0>;
> >> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
> >> >> > + interrupt-map-mask = <0 0 0 7>;
> >> >> > + interrupt-map = <0 0 0 1 &pcie2_intc 0>,
> >> >> > + <0 0 0 2 &pcie2_intc 1>,
> >> >> > + <0 0 0 3 &pcie2_intc 2>,
> >> >> > + <0 0 0 4 &pcie2_intc 3>;
> >> >> > marvell,pcie-port = <1>;
> >> >> > marvell,pcie-lane = <0>;
> >> >> > clocks = <&gateclk 5>;
> >> >> > status = "disabled";
> >> >> > + pcie2_intc: interrupt-controller {
> >> >> > + interrupt-controller;
> >> >> > + #interrupt-cells = <1>;
> >> >> > + };
> >> >> > };
> >> >> >
> >> >> > /* x1 port */
> >> >> > @@ -107,16 +125,25 @@
> >> >> > reg = <0x1800 0 0 0 0>;
> >> >> > #address-cells = <3>;
> >> >> > #size-cells = <2>;
> >> >> > + interrupt-names = "intx";
> >> >> > + interrupts-extended = <&gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
> >> >> > #interrupt-cells = <1>;
> >> >> > ranges = <0x82000000 0 0 0x82000000 0x3 0 1 0
> >> >> > 0x81000000 0 0 0x81000000 0x3 0 1 0>;
> >> >> > bus-range = <0x00 0xff>;
> >> >> > - interrupt-map-mask = <0 0 0 0>;
> >> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
> >> >> > + interrupt-map-mask = <0 0 0 7>;
> >> >> > + interrupt-map = <0 0 0 1 &pcie3_intc 0>,
> >> >> > + <0 0 0 2 &pcie3_intc 1>,
> >> >> > + <0 0 0 3 &pcie3_intc 2>,
> >> >> > + <0 0 0 4 &pcie3_intc 3>;
> >> >> > marvell,pcie-port = <2>;
> >> >> > marvell,pcie-lane = <0>;
> >> >> > clocks = <&gateclk 6>;
> >> >> > status = "disabled";
> >> >> > + pcie3_intc: interrupt-controller {
> >> >> > + interrupt-controller;
> >> >> > + #interrupt-cells = <1>;
> >> >> > + };
> >> >> > };
> >> >> >
> >> >> > /*
> >> >> > @@ -129,16 +156,25 @@
> >> >> > reg = <0x2000 0 0 0 0>;
> >> >> > #address-cells = <3>;
> >> >> > #size-cells = <2>;
> >> >> > + interrupt-names = "intx";
> >> >> > + interrupts-extended = <&gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
> >> >> > #interrupt-cells = <1>;
> >> >> > ranges = <0x82000000 0 0 0x82000000 0x4 0 1 0
> >> >> > 0x81000000 0 0 0x81000000 0x4 0 1 0>;
> >> >> > bus-range = <0x00 0xff>;
> >> >> > - interrupt-map-mask = <0 0 0 0>;
> >> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
> >> >> > + interrupt-map-mask = <0 0 0 7>;
> >> >> > + interrupt-map = <0 0 0 1 &pcie4_intc 0>,
> >> >> > + <0 0 0 2 &pcie4_intc 1>,
> >> >> > + <0 0 0 3 &pcie4_intc 2>,
> >> >> > + <0 0 0 4 &pcie4_intc 3>;
> >> >> > marvell,pcie-port = <3>;
> >> >> > marvell,pcie-lane = <0>;
> >> >> > clocks = <&gateclk 7>;
> >> >> > status = "disabled";
> >> >> > + pcie4_intc: interrupt-controller {
> >> >> > + interrupt-controller;
> >> >> > + #interrupt-cells = <1>;
> >> >> > + };
> >> >> > };
> >> >> > };
> >> >> > };
> >> >> > --
> >> >> > 2.20.1
> >> >> >
> >> >>
> >> >> --
> >> >> Gregory Clement, Bootlin
> >> >> Embedded Linux and Kernel engineering
> >> >> http://bootlin.com
> >>
> >> --
> >> Gregory Clement, Bootlin
> >> Embedded Linux and Kernel engineering
> >> http://bootlin.com
On Friday 11 February 2022 18:52:02 Pali Rohár wrote:
> On Friday 11 February 2022 17:19:17 Lorenzo Pieralisi wrote:
> > On Wed, Jan 12, 2022 at 04:18:13PM +0100, Pali Rohár wrote:
> > > This adds support for legacy INTx interrupts received from other PCIe
> > > devices and which are reported by a new INTx irq chip.
> > >
> > > With this change, kernel can distinguish between INTA, INTB, INTC and INTD
> > > interrupts.
> > >
> > > Note that for this support, device tree files has to be properly adjusted
> > > to provide "interrupts" or "interrupts-extended" property with intx
> > > interrupt source, "interrupt-names" property with "intx" string and also
> > > 'interrupt-controller' subnode must be defined.
> > >
> > > If device tree files do not provide these nodes then driver would work as
> > > before.
> >
> > Nit: this information is not useful. DT rules are written in DT
> > bindings, not in kernel commit logs. All I am saying is that firmware
> > developers should not have to read this log to write firmware.
>
> It was not intended for firmware developers, but for reviewers of this
> patch to understand, what is happening in code and that with old DT
> files this patch does not change driver behavior (= work as before).
>
> > > Signed-off-by: Pali Rohár <[email protected]>
> > > ---
> > > drivers/pci/controller/pci-mvebu.c | 185 +++++++++++++++++++++++++++--
> > > 1 file changed, 177 insertions(+), 8 deletions(-)
> > >
> > > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > > index 1e90ab888075..dbb6ecb4cb70 100644
> > > --- a/drivers/pci/controller/pci-mvebu.c
> > > +++ b/drivers/pci/controller/pci-mvebu.c
> > > @@ -54,9 +54,10 @@
> > > PCIE_CONF_ADDR_EN)
> > > #define PCIE_CONF_DATA_OFF 0x18fc
> > > #define PCIE_INT_CAUSE_OFF 0x1900
> > > +#define PCIE_INT_UNMASK_OFF 0x1910
> >
> > Nit: I understand it is tempting but here you are redefining or better
> > giving a proper label to a register. Separate patch please.
>
> Ok!
>
> > > +#define PCIE_INT_INTX(i) BIT(24+i)
> > > #define PCIE_INT_PM_PME BIT(28)
> > > -#define PCIE_MASK_OFF 0x1910
> >
> > See above.
> >
> > > -#define PCIE_MASK_ENABLE_INTS 0x0f000000
> > > +#define PCIE_INT_ALL_MASK GENMASK(31, 0)
> > > #define PCIE_CTRL_OFF 0x1a00
> > > #define PCIE_CTRL_X1_MODE 0x0001
> > > #define PCIE_CTRL_RC_MODE BIT(1)
> > > @@ -110,6 +111,9 @@ struct mvebu_pcie_port {
> > > struct mvebu_pcie_window iowin;
> > > u32 saved_pcie_stat;
> > > struct resource regs;
> > > + struct irq_domain *intx_irq_domain;
> > > + raw_spinlock_t irq_lock;
> > > + int intx_irq;
> > > };
> > >
> > > static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
> > > @@ -235,7 +239,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
> > >
> > > static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > {
> > > - u32 ctrl, lnkcap, cmd, dev_rev, mask;
> > > + u32 ctrl, lnkcap, cmd, dev_rev, unmask;
> > >
> > > /* Setup PCIe controller to Root Complex mode. */
> > > ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> > > @@ -288,10 +292,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > /* Point PCIe unit MBUS decode windows to DRAM space. */
> > > mvebu_pcie_setup_wins(port);
> > >
> > > - /* Enable interrupt lines A-D. */
> > > - mask = mvebu_readl(port, PCIE_MASK_OFF);
> > > - mask |= PCIE_MASK_ENABLE_INTS;
> > > - mvebu_writel(port, mask, PCIE_MASK_OFF);
> > > + /* Mask all interrupt sources. */
> > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > > +
> > > + /* Clear all interrupt causes. */
> > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > > +
> > > + if (port->intx_irq <= 0) {
> > > + /*
> > > + * When neither "summary" interrupt, nor "intx" interrupt was
> > > + * specified in DT then unmask all legacy INTx interrupts as in
> > > + * this case driver does not provide a way for masking and
> > > + * unmasking of individual legacy INTx interrupts. In this case
> > > + * all interrupts, including legacy INTx are reported via one
> > > + * shared GIC source and therefore kernel cannot distinguish
> > > + * which individual legacy INTx was triggered. These interrupts
> > > + * are shared, so it should not cause any issue. Just
> > > + * performance penalty as every PCIe interrupt handler needs to
> > > + * be called when some interrupt is triggered.
> > > + */
> >
> > This comment applies to current mainline right (ie it describes how
> > current mainline handles INTx) ? IMO you should split it out in a
> > separate patch.
>
> This above comment describe what happens in if-branch when intx_irq is
> not set (as written in comment "when intx interrupt was not specified in
> DT"). You are right that this is also the behavior in the current
> mainline.
>
> I'm not sure if this comment can be split out as support for "intx"
> interrupt is in this patch.
>
> > I understand it is hard but a patch is a logical _change_, this
> > comment is a change per se, it is a clarification on current
> > behaviour.
>
> Ok, I could try to split this comment into two patches, but part about
> if-branch comment needs to stay in "this" patch.
I have done it locally.
Let me know when I should resend this patch series and I will include
into it also these changes.
> > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > + unmask |= PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
> > > + PCIE_INT_INTX(2) | PCIE_INT_INTX(3);
> > > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > > + }
> > > }
> > >
> > > static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
> > > @@ -924,6 +948,108 @@ static struct pci_ops mvebu_pcie_ops = {
> > > .write = mvebu_pcie_wr_conf,
> > > };
> > >
> > > +static void mvebu_pcie_intx_irq_mask(struct irq_data *d)
> > > +{
> > > + struct mvebu_pcie_port *port = d->domain->host_data;
> > > + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> > > + unsigned long flags;
> > > + u32 unmask;
> > > +
> > > + raw_spin_lock_irqsave(&port->irq_lock, flags);
> > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > + unmask &= ~PCIE_INT_INTX(hwirq);
> > > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > > + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> > > +}
> > > +
> > > +static void mvebu_pcie_intx_irq_unmask(struct irq_data *d)
> > > +{
> > > + struct mvebu_pcie_port *port = d->domain->host_data;
> > > + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> > > + unsigned long flags;
> > > + u32 unmask;
> > > +
> > > + raw_spin_lock_irqsave(&port->irq_lock, flags);
> > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > + unmask |= PCIE_INT_INTX(hwirq);
> > > + mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
> > > + raw_spin_unlock_irqrestore(&port->irq_lock, flags);
> > > +}
> > > +
> > > +static struct irq_chip intx_irq_chip = {
> > > + .name = "mvebu-INTx",
> > > + .irq_mask = mvebu_pcie_intx_irq_mask,
> > > + .irq_unmask = mvebu_pcie_intx_irq_unmask,
> > > +};
> > > +
> > > +static int mvebu_pcie_intx_irq_map(struct irq_domain *h,
> > > + unsigned int virq, irq_hw_number_t hwirq)
> > > +{
> > > + struct mvebu_pcie_port *port = h->host_data;
> > > +
> > > + irq_set_status_flags(virq, IRQ_LEVEL);
> > > + irq_set_chip_and_handler(virq, &intx_irq_chip, handle_level_irq);
> > > + irq_set_chip_data(virq, port);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static const struct irq_domain_ops mvebu_pcie_intx_irq_domain_ops = {
> > > + .map = mvebu_pcie_intx_irq_map,
> > > + .xlate = irq_domain_xlate_onecell,
> > > +};
> > > +
> > > +static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
> > > +{
> > > + struct device *dev = &port->pcie->pdev->dev;
> > > + struct device_node *pcie_intc_node;
> > > +
> > > + raw_spin_lock_init(&port->irq_lock);
> > > +
> > > + pcie_intc_node = of_get_next_child(port->dn, NULL);
> > > + if (!pcie_intc_node) {
> > > + dev_err(dev, "No PCIe Intc node found for %s\n", port->name);
> > > + return -ENODEV;
> > > + }
> > > +
> > > + port->intx_irq_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX,
> > > + &mvebu_pcie_intx_irq_domain_ops,
> > > + port);
> > > + of_node_put(pcie_intc_node);
> > > + if (!port->intx_irq_domain) {
> > > + dev_err(dev, "Failed to get INTx IRQ domain for %s\n", port->name);
> > > + return -ENOMEM;
> > > + }
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static void mvebu_pcie_irq_handler(struct irq_desc *desc)
> > > +{
> > > + struct mvebu_pcie_port *port = irq_desc_get_handler_data(desc);
> > > + struct irq_chip *chip = irq_desc_get_chip(desc);
> > > + struct device *dev = &port->pcie->pdev->dev;
> > > + u32 cause, unmask, status;
> > > + int i;
> > > +
> > > + chained_irq_enter(chip, desc);
> > > +
> > > + cause = mvebu_readl(port, PCIE_INT_CAUSE_OFF);
> > > + unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
> > > + status = cause & unmask;
> > > +
> > > + /* Process legacy INTx interrupts */
> > > + for (i = 0; i < PCI_NUM_INTX; i++) {
> > > + if (!(status & PCIE_INT_INTX(i)))
> > > + continue;
> > > +
> > > + if (generic_handle_domain_irq(port->intx_irq_domain, i) == -EINVAL)
> > > + dev_err_ratelimited(dev, "unexpected INT%c IRQ\n", (char)i+'A');
> > > + }
> > > +
> > > + chained_irq_exit(chip, desc);
> > > +}
> > > +
> > > static int mvebu_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
> > > {
> > > /* Interrupt support on mvebu emulated bridges is not implemented yet */
> > > @@ -1121,6 +1247,21 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
> > > port->io_attr = -1;
> > > }
> > >
> > > + /*
> > > + * Old DT bindings do not contain "intx" interrupt
> > > + * so do not fail probing driver when interrupt does not exist.
> > > + */
> > > + port->intx_irq = of_irq_get_byname(child, "intx");
> > > + if (port->intx_irq == -EPROBE_DEFER) {
> > > + ret = port->intx_irq;
> > > + goto err;
> > > + }
> > > + if (port->intx_irq <= 0) {
> > > + dev_warn(dev, "%s: legacy INTx interrupts cannot be masked individually, "
> > > + "%pOF does not contain intx interrupt\n",
> > > + port->name, child);
> >
> > Here you end up with a new warning on existing firmware. Is it
> > legitimate ? I would remove the dev_warn().
>
> I added this warning in v2 because Marc wanted it.
>
> Should I (again) remove it in v3?
>
> > Rob certainly has more insightful advice on this.
> >
> > Thanks,
> > Lorenzo
> >
> > > + }
> > > +
> > > reset_gpio = of_get_named_gpio_flags(child, "reset-gpios", 0, &flags);
> > > if (reset_gpio == -EPROBE_DEFER) {
> > > ret = reset_gpio;
> > > @@ -1317,6 +1458,7 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
> > >
> > > for (i = 0; i < pcie->nports; i++) {
> > > struct mvebu_pcie_port *port = &pcie->ports[i];
> > > + int irq = port->intx_irq;
> > >
> > > child = port->dn;
> > > if (!child)
> > > @@ -1344,6 +1486,22 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
> > > continue;
> > > }
> > >
> > > + if (irq > 0) {
> > > + ret = mvebu_pcie_init_irq_domain(port);
> > > + if (ret) {
> > > + dev_err(dev, "%s: cannot init irq domain\n",
> > > + port->name);
> > > + pci_bridge_emul_cleanup(&port->bridge);
> > > + devm_iounmap(dev, port->base);
> > > + port->base = NULL;
> > > + mvebu_pcie_powerdown(port);
> > > + continue;
> > > + }
> > > + irq_set_chained_handler_and_data(irq,
> > > + mvebu_pcie_irq_handler,
> > > + port);
> > > + }
> > > +
> > > /*
> > > * PCIe topology exported by mvebu hw is quite complicated. In
> > > * reality has something like N fully independent host bridges
> > > @@ -1448,6 +1606,7 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
> > >
> > > for (i = 0; i < pcie->nports; i++) {
> > > struct mvebu_pcie_port *port = &pcie->ports[i];
> > > + int irq = port->intx_irq;
> > >
> > > if (!port->base)
> > > continue;
> > > @@ -1458,7 +1617,17 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
> > > mvebu_writel(port, cmd, PCIE_CMD_OFF);
> > >
> > > /* Mask all interrupt sources. */
> > > - mvebu_writel(port, 0, PCIE_MASK_OFF);
> > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > > +
> > > + /* Clear all interrupt causes. */
> > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > > +
> > > + if (irq > 0)
> > > + irq_set_chained_handler_and_data(irq, NULL, NULL);
> > > +
> > > + /* Remove IRQ domains. */
> > > + if (port->intx_irq_domain)
> > > + irq_domain_remove(port->intx_irq_domain);
> > >
> > > /* Free config space for emulated root bridge. */
> > > pci_bridge_emul_cleanup(&port->bridge);
> > > --
> > > 2.20.1
> > >
Successfully tested on my custom A388 system with two PCI express slots.
If you wish you can add a:
Tested-by: Luis Mendes <[email protected]>
On Tue, Feb 15, 2022 at 10:52 AM Pali Rohár <[email protected]> wrote:
>
> Hello! armada-388.dtsi file has #include "armada-385.dtsi" line and
> therefore is already covered by this my patch.
>
> Gregory's question was about A380.
>
> But if you want, you can test this patch series (which already covers
> A388) on your A388 HW. It is still better to do tests on more HW.
>
> On Tuesday 15 February 2022 10:48:17 Luís Mendes wrote:
> > Hello,
> >
> > Sorry for jumping in the conversation, but I read this thread and I
> > have an Armada A388 HW so I can test it, if desired.
> >
> > Luís
> >
> >
> > On Tue, Feb 15, 2022 at 10:47 AM Luís Mendes <[email protected]> wrote:
> > >
> > > Hello,
> > >
> > > Sorry for jumping in the conversation, but I read this thread and I have an Armada A388 HW so I can test it, if desired.
> > >
> > > Luís
> > >
> > > On Mon, Feb 14, 2022 at 7:57 PM Gregory CLEMENT <[email protected]> wrote:
> > >>
> > >> Hello,
> > >>
> > >> > On Monday 14 February 2022 16:07:13 Gregory CLEMENT wrote:
> > >> >> Hello Pali,
> > >> >>
> > >> >> > With this change legacy INTA, INTB, INTC and INTD interrupts are reported
> > >> >> > separately and not mixed into one Linux virq source anymore.
> > >> >> >
> > >> >> > Signed-off-by: Pali Rohár <[email protected]>
> > >> >> > ---
> > >> >> > arch/arm/boot/dts/armada-385.dtsi | 52 ++++++++++++++++++++++++++-----
> > >> >>
> > >> >> Is there any reason for not doing the same change in armada-380.dtsi ?
> > >> >
> > >> > I do not have A380 HW, so I did this change only for A385 which I have
> > >> > tested.
> > >>
> > >> OK fair enough.
> > >>
> > >> So you can add my
> > >> Acked-by: Gregory CLEMENT <[email protected]>
> > >>
> > >> Moreover to keep biscetability this patch should be merged after the
> > >> support in the driver. So the easier is to let merge it through the PCI
> > >> subsystem with the other patches from this series. I do not think there
> > >> will be any other changes in this file so there won't be any merge
> > >> conflicts.
> > >>
> > >> Thanks,
> > >>
> > >> Grégory
> > >>
> > >>
> > >> >
> > >> >> Grégory
> > >> >>
> > >> >> > 1 file changed, 44 insertions(+), 8 deletions(-)
> > >> >> >
> > >> >> > diff --git a/arch/arm/boot/dts/armada-385.dtsi b/arch/arm/boot/dts/armada-385.dtsi
> > >> >> > index f0022d10c715..83392b92dae2 100644
> > >> >> > --- a/arch/arm/boot/dts/armada-385.dtsi
> > >> >> > +++ b/arch/arm/boot/dts/armada-385.dtsi
> > >> >> > @@ -69,16 +69,25 @@
> > >> >> > reg = <0x0800 0 0 0 0>;
> > >> >> > #address-cells = <3>;
> > >> >> > #size-cells = <2>;
> > >> >> > + interrupt-names = "intx";
> > >> >> > + interrupts-extended = <&gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
> > >> >> > #interrupt-cells = <1>;
> > >> >> > ranges = <0x82000000 0 0 0x82000000 0x1 0 1 0
> > >> >> > 0x81000000 0 0 0x81000000 0x1 0 1 0>;
> > >> >> > bus-range = <0x00 0xff>;
> > >> >> > - interrupt-map-mask = <0 0 0 0>;
> > >> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
> > >> >> > + interrupt-map-mask = <0 0 0 7>;
> > >> >> > + interrupt-map = <0 0 0 1 &pcie1_intc 0>,
> > >> >> > + <0 0 0 2 &pcie1_intc 1>,
> > >> >> > + <0 0 0 3 &pcie1_intc 2>,
> > >> >> > + <0 0 0 4 &pcie1_intc 3>;
> > >> >> > marvell,pcie-port = <0>;
> > >> >> > marvell,pcie-lane = <0>;
> > >> >> > clocks = <&gateclk 8>;
> > >> >> > status = "disabled";
> > >> >> > + pcie1_intc: interrupt-controller {
> > >> >> > + interrupt-controller;
> > >> >> > + #interrupt-cells = <1>;
> > >> >> > + };
> > >> >> > };
> > >> >> >
> > >> >> > /* x1 port */
> > >> >> > @@ -88,16 +97,25 @@
> > >> >> > reg = <0x1000 0 0 0 0>;
> > >> >> > #address-cells = <3>;
> > >> >> > #size-cells = <2>;
> > >> >> > + interrupt-names = "intx";
> > >> >> > + interrupts-extended = <&gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
> > >> >> > #interrupt-cells = <1>;
> > >> >> > ranges = <0x82000000 0 0 0x82000000 0x2 0 1 0
> > >> >> > 0x81000000 0 0 0x81000000 0x2 0 1 0>;
> > >> >> > bus-range = <0x00 0xff>;
> > >> >> > - interrupt-map-mask = <0 0 0 0>;
> > >> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
> > >> >> > + interrupt-map-mask = <0 0 0 7>;
> > >> >> > + interrupt-map = <0 0 0 1 &pcie2_intc 0>,
> > >> >> > + <0 0 0 2 &pcie2_intc 1>,
> > >> >> > + <0 0 0 3 &pcie2_intc 2>,
> > >> >> > + <0 0 0 4 &pcie2_intc 3>;
> > >> >> > marvell,pcie-port = <1>;
> > >> >> > marvell,pcie-lane = <0>;
> > >> >> > clocks = <&gateclk 5>;
> > >> >> > status = "disabled";
> > >> >> > + pcie2_intc: interrupt-controller {
> > >> >> > + interrupt-controller;
> > >> >> > + #interrupt-cells = <1>;
> > >> >> > + };
> > >> >> > };
> > >> >> >
> > >> >> > /* x1 port */
> > >> >> > @@ -107,16 +125,25 @@
> > >> >> > reg = <0x1800 0 0 0 0>;
> > >> >> > #address-cells = <3>;
> > >> >> > #size-cells = <2>;
> > >> >> > + interrupt-names = "intx";
> > >> >> > + interrupts-extended = <&gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
> > >> >> > #interrupt-cells = <1>;
> > >> >> > ranges = <0x82000000 0 0 0x82000000 0x3 0 1 0
> > >> >> > 0x81000000 0 0 0x81000000 0x3 0 1 0>;
> > >> >> > bus-range = <0x00 0xff>;
> > >> >> > - interrupt-map-mask = <0 0 0 0>;
> > >> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
> > >> >> > + interrupt-map-mask = <0 0 0 7>;
> > >> >> > + interrupt-map = <0 0 0 1 &pcie3_intc 0>,
> > >> >> > + <0 0 0 2 &pcie3_intc 1>,
> > >> >> > + <0 0 0 3 &pcie3_intc 2>,
> > >> >> > + <0 0 0 4 &pcie3_intc 3>;
> > >> >> > marvell,pcie-port = <2>;
> > >> >> > marvell,pcie-lane = <0>;
> > >> >> > clocks = <&gateclk 6>;
> > >> >> > status = "disabled";
> > >> >> > + pcie3_intc: interrupt-controller {
> > >> >> > + interrupt-controller;
> > >> >> > + #interrupt-cells = <1>;
> > >> >> > + };
> > >> >> > };
> > >> >> >
> > >> >> > /*
> > >> >> > @@ -129,16 +156,25 @@
> > >> >> > reg = <0x2000 0 0 0 0>;
> > >> >> > #address-cells = <3>;
> > >> >> > #size-cells = <2>;
> > >> >> > + interrupt-names = "intx";
> > >> >> > + interrupts-extended = <&gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
> > >> >> > #interrupt-cells = <1>;
> > >> >> > ranges = <0x82000000 0 0 0x82000000 0x4 0 1 0
> > >> >> > 0x81000000 0 0 0x81000000 0x4 0 1 0>;
> > >> >> > bus-range = <0x00 0xff>;
> > >> >> > - interrupt-map-mask = <0 0 0 0>;
> > >> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
> > >> >> > + interrupt-map-mask = <0 0 0 7>;
> > >> >> > + interrupt-map = <0 0 0 1 &pcie4_intc 0>,
> > >> >> > + <0 0 0 2 &pcie4_intc 1>,
> > >> >> > + <0 0 0 3 &pcie4_intc 2>,
> > >> >> > + <0 0 0 4 &pcie4_intc 3>;
> > >> >> > marvell,pcie-port = <3>;
> > >> >> > marvell,pcie-lane = <0>;
> > >> >> > clocks = <&gateclk 7>;
> > >> >> > status = "disabled";
> > >> >> > + pcie4_intc: interrupt-controller {
> > >> >> > + interrupt-controller;
> > >> >> > + #interrupt-cells = <1>;
> > >> >> > + };
> > >> >> > };
> > >> >> > };
> > >> >> > };
> > >> >> > --
> > >> >> > 2.20.1
> > >> >> >
> > >> >>
> > >> >> --
> > >> >> Gregory Clement, Bootlin
> > >> >> Embedded Linux and Kernel engineering
> > >> >> http://bootlin.com
> > >>
> > >> --
> > >> Gregory Clement, Bootlin
> > >> Embedded Linux and Kernel engineering
> > >> http://bootlin.com
Perfect, thanks!
On Friday 18 February 2022 21:53:43 Luís Mendes wrote:
> Successfully tested on my custom A388 system with two PCI express slots.
>
> If you wish you can add a:
> Tested-by: Luis Mendes <[email protected]>
>
> On Tue, Feb 15, 2022 at 10:52 AM Pali Rohár <[email protected]> wrote:
> >
> > Hello! armada-388.dtsi file has #include "armada-385.dtsi" line and
> > therefore is already covered by this my patch.
> >
> > Gregory's question was about A380.
> >
> > But if you want, you can test this patch series (which already covers
> > A388) on your A388 HW. It is still better to do tests on more HW.
> >
> > On Tuesday 15 February 2022 10:48:17 Luís Mendes wrote:
> > > Hello,
> > >
> > > Sorry for jumping in the conversation, but I read this thread and I
> > > have an Armada A388 HW so I can test it, if desired.
> > >
> > > Luís
> > >
> > >
> > > On Tue, Feb 15, 2022 at 10:47 AM Luís Mendes <[email protected]> wrote:
> > > >
> > > > Hello,
> > > >
> > > > Sorry for jumping in the conversation, but I read this thread and I have an Armada A388 HW so I can test it, if desired.
> > > >
> > > > Luís
> > > >
> > > > On Mon, Feb 14, 2022 at 7:57 PM Gregory CLEMENT <[email protected]> wrote:
> > > >>
> > > >> Hello,
> > > >>
> > > >> > On Monday 14 February 2022 16:07:13 Gregory CLEMENT wrote:
> > > >> >> Hello Pali,
> > > >> >>
> > > >> >> > With this change legacy INTA, INTB, INTC and INTD interrupts are reported
> > > >> >> > separately and not mixed into one Linux virq source anymore.
> > > >> >> >
> > > >> >> > Signed-off-by: Pali Rohár <[email protected]>
> > > >> >> > ---
> > > >> >> > arch/arm/boot/dts/armada-385.dtsi | 52 ++++++++++++++++++++++++++-----
> > > >> >>
> > > >> >> Is there any reason for not doing the same change in armada-380.dtsi ?
> > > >> >
> > > >> > I do not have A380 HW, so I did this change only for A385 which I have
> > > >> > tested.
> > > >>
> > > >> OK fair enough.
> > > >>
> > > >> So you can add my
> > > >> Acked-by: Gregory CLEMENT <[email protected]>
> > > >>
> > > >> Moreover to keep biscetability this patch should be merged after the
> > > >> support in the driver. So the easier is to let merge it through the PCI
> > > >> subsystem with the other patches from this series. I do not think there
> > > >> will be any other changes in this file so there won't be any merge
> > > >> conflicts.
> > > >>
> > > >> Thanks,
> > > >>
> > > >> Grégory
> > > >>
> > > >>
> > > >> >
> > > >> >> Grégory
> > > >> >>
> > > >> >> > 1 file changed, 44 insertions(+), 8 deletions(-)
> > > >> >> >
> > > >> >> > diff --git a/arch/arm/boot/dts/armada-385.dtsi b/arch/arm/boot/dts/armada-385.dtsi
> > > >> >> > index f0022d10c715..83392b92dae2 100644
> > > >> >> > --- a/arch/arm/boot/dts/armada-385.dtsi
> > > >> >> > +++ b/arch/arm/boot/dts/armada-385.dtsi
> > > >> >> > @@ -69,16 +69,25 @@
> > > >> >> > reg = <0x0800 0 0 0 0>;
> > > >> >> > #address-cells = <3>;
> > > >> >> > #size-cells = <2>;
> > > >> >> > + interrupt-names = "intx";
> > > >> >> > + interrupts-extended = <&gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
> > > >> >> > #interrupt-cells = <1>;
> > > >> >> > ranges = <0x82000000 0 0 0x82000000 0x1 0 1 0
> > > >> >> > 0x81000000 0 0 0x81000000 0x1 0 1 0>;
> > > >> >> > bus-range = <0x00 0xff>;
> > > >> >> > - interrupt-map-mask = <0 0 0 0>;
> > > >> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
> > > >> >> > + interrupt-map-mask = <0 0 0 7>;
> > > >> >> > + interrupt-map = <0 0 0 1 &pcie1_intc 0>,
> > > >> >> > + <0 0 0 2 &pcie1_intc 1>,
> > > >> >> > + <0 0 0 3 &pcie1_intc 2>,
> > > >> >> > + <0 0 0 4 &pcie1_intc 3>;
> > > >> >> > marvell,pcie-port = <0>;
> > > >> >> > marvell,pcie-lane = <0>;
> > > >> >> > clocks = <&gateclk 8>;
> > > >> >> > status = "disabled";
> > > >> >> > + pcie1_intc: interrupt-controller {
> > > >> >> > + interrupt-controller;
> > > >> >> > + #interrupt-cells = <1>;
> > > >> >> > + };
> > > >> >> > };
> > > >> >> >
> > > >> >> > /* x1 port */
> > > >> >> > @@ -88,16 +97,25 @@
> > > >> >> > reg = <0x1000 0 0 0 0>;
> > > >> >> > #address-cells = <3>;
> > > >> >> > #size-cells = <2>;
> > > >> >> > + interrupt-names = "intx";
> > > >> >> > + interrupts-extended = <&gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
> > > >> >> > #interrupt-cells = <1>;
> > > >> >> > ranges = <0x82000000 0 0 0x82000000 0x2 0 1 0
> > > >> >> > 0x81000000 0 0 0x81000000 0x2 0 1 0>;
> > > >> >> > bus-range = <0x00 0xff>;
> > > >> >> > - interrupt-map-mask = <0 0 0 0>;
> > > >> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
> > > >> >> > + interrupt-map-mask = <0 0 0 7>;
> > > >> >> > + interrupt-map = <0 0 0 1 &pcie2_intc 0>,
> > > >> >> > + <0 0 0 2 &pcie2_intc 1>,
> > > >> >> > + <0 0 0 3 &pcie2_intc 2>,
> > > >> >> > + <0 0 0 4 &pcie2_intc 3>;
> > > >> >> > marvell,pcie-port = <1>;
> > > >> >> > marvell,pcie-lane = <0>;
> > > >> >> > clocks = <&gateclk 5>;
> > > >> >> > status = "disabled";
> > > >> >> > + pcie2_intc: interrupt-controller {
> > > >> >> > + interrupt-controller;
> > > >> >> > + #interrupt-cells = <1>;
> > > >> >> > + };
> > > >> >> > };
> > > >> >> >
> > > >> >> > /* x1 port */
> > > >> >> > @@ -107,16 +125,25 @@
> > > >> >> > reg = <0x1800 0 0 0 0>;
> > > >> >> > #address-cells = <3>;
> > > >> >> > #size-cells = <2>;
> > > >> >> > + interrupt-names = "intx";
> > > >> >> > + interrupts-extended = <&gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
> > > >> >> > #interrupt-cells = <1>;
> > > >> >> > ranges = <0x82000000 0 0 0x82000000 0x3 0 1 0
> > > >> >> > 0x81000000 0 0 0x81000000 0x3 0 1 0>;
> > > >> >> > bus-range = <0x00 0xff>;
> > > >> >> > - interrupt-map-mask = <0 0 0 0>;
> > > >> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
> > > >> >> > + interrupt-map-mask = <0 0 0 7>;
> > > >> >> > + interrupt-map = <0 0 0 1 &pcie3_intc 0>,
> > > >> >> > + <0 0 0 2 &pcie3_intc 1>,
> > > >> >> > + <0 0 0 3 &pcie3_intc 2>,
> > > >> >> > + <0 0 0 4 &pcie3_intc 3>;
> > > >> >> > marvell,pcie-port = <2>;
> > > >> >> > marvell,pcie-lane = <0>;
> > > >> >> > clocks = <&gateclk 6>;
> > > >> >> > status = "disabled";
> > > >> >> > + pcie3_intc: interrupt-controller {
> > > >> >> > + interrupt-controller;
> > > >> >> > + #interrupt-cells = <1>;
> > > >> >> > + };
> > > >> >> > };
> > > >> >> >
> > > >> >> > /*
> > > >> >> > @@ -129,16 +156,25 @@
> > > >> >> > reg = <0x2000 0 0 0 0>;
> > > >> >> > #address-cells = <3>;
> > > >> >> > #size-cells = <2>;
> > > >> >> > + interrupt-names = "intx";
> > > >> >> > + interrupts-extended = <&gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
> > > >> >> > #interrupt-cells = <1>;
> > > >> >> > ranges = <0x82000000 0 0 0x82000000 0x4 0 1 0
> > > >> >> > 0x81000000 0 0 0x81000000 0x4 0 1 0>;
> > > >> >> > bus-range = <0x00 0xff>;
> > > >> >> > - interrupt-map-mask = <0 0 0 0>;
> > > >> >> > - interrupt-map = <0 0 0 0 &gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
> > > >> >> > + interrupt-map-mask = <0 0 0 7>;
> > > >> >> > + interrupt-map = <0 0 0 1 &pcie4_intc 0>,
> > > >> >> > + <0 0 0 2 &pcie4_intc 1>,
> > > >> >> > + <0 0 0 3 &pcie4_intc 2>,
> > > >> >> > + <0 0 0 4 &pcie4_intc 3>;
> > > >> >> > marvell,pcie-port = <3>;
> > > >> >> > marvell,pcie-lane = <0>;
> > > >> >> > clocks = <&gateclk 7>;
> > > >> >> > status = "disabled";
> > > >> >> > + pcie4_intc: interrupt-controller {
> > > >> >> > + interrupt-controller;
> > > >> >> > + #interrupt-cells = <1>;
> > > >> >> > + };
> > > >> >> > };
> > > >> >> > };
> > > >> >> > };
> > > >> >> > --
> > > >> >> > 2.20.1
> > > >> >> >
> > > >> >>
> > > >> >> --
> > > >> >> Gregory Clement, Bootlin
> > > >> >> Embedded Linux and Kernel engineering
> > > >> >> http://bootlin.com
> > > >>
> > > >> --
> > > >> Gregory Clement, Bootlin
> > > >> Embedded Linux and Kernel engineering
> > > >> http://bootlin.com
On Thu, Feb 17, 2022 at 12:40:39AM +0100, Pali Roh?r wrote:
> On Friday 11 February 2022 18:52:02 Pali Roh?r wrote:
> > On Friday 11 February 2022 17:19:17 Lorenzo Pieralisi wrote:
> > > On Wed, Jan 12, 2022 at 04:18:13PM +0100, Pali Roh?r wrote:
> > > > This adds support for legacy INTx interrupts received from other PCIe
> > > > devices and which are reported by a new INTx irq chip.
> > > >
> > > > With this change, kernel can distinguish between INTA, INTB, INTC and INTD
> > > > interrupts.
> > > >
> > > > Note that for this support, device tree files has to be properly adjusted
> > > > to provide "interrupts" or "interrupts-extended" property with intx
> > > > interrupt source, "interrupt-names" property with "intx" string and also
> > > > 'interrupt-controller' subnode must be defined.
> > > >
> > > > If device tree files do not provide these nodes then driver would work as
> > > > before.
> > >
> > > Nit: this information is not useful. DT rules are written in DT
> > > bindings, not in kernel commit logs. All I am saying is that firmware
> > > developers should not have to read this log to write firmware.
> >
> > It was not intended for firmware developers, but for reviewers of this
> > patch to understand, what is happening in code and that with old DT
> > files this patch does not change driver behavior (= work as before).
> >
> > > > Signed-off-by: Pali Roh?r <[email protected]>
> > > > ---
> > > > drivers/pci/controller/pci-mvebu.c | 185 +++++++++++++++++++++++++++--
> > > > 1 file changed, 177 insertions(+), 8 deletions(-)
> > > >
> > > > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > > > index 1e90ab888075..dbb6ecb4cb70 100644
> > > > --- a/drivers/pci/controller/pci-mvebu.c
> > > > +++ b/drivers/pci/controller/pci-mvebu.c
> > > > @@ -54,9 +54,10 @@
> > > > PCIE_CONF_ADDR_EN)
> > > > #define PCIE_CONF_DATA_OFF 0x18fc
> > > > #define PCIE_INT_CAUSE_OFF 0x1900
> > > > +#define PCIE_INT_UNMASK_OFF 0x1910
> > >
> > > Nit: I understand it is tempting but here you are redefining or better
> > > giving a proper label to a register. Separate patch please.
> >
> > Ok!
> >
> > > > +#define PCIE_INT_INTX(i) BIT(24+i)
> > > > #define PCIE_INT_PM_PME BIT(28)
> > > > -#define PCIE_MASK_OFF 0x1910
> > >
> > > See above.
> > >
> > > > -#define PCIE_MASK_ENABLE_INTS 0x0f000000
> > > > +#define PCIE_INT_ALL_MASK GENMASK(31, 0)
> > > > #define PCIE_CTRL_OFF 0x1a00
> > > > #define PCIE_CTRL_X1_MODE 0x0001
> > > > #define PCIE_CTRL_RC_MODE BIT(1)
> > > > @@ -110,6 +111,9 @@ struct mvebu_pcie_port {
> > > > struct mvebu_pcie_window iowin;
> > > > u32 saved_pcie_stat;
> > > > struct resource regs;
> > > > + struct irq_domain *intx_irq_domain;
> > > > + raw_spinlock_t irq_lock;
> > > > + int intx_irq;
> > > > };
> > > >
> > > > static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
> > > > @@ -235,7 +239,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
> > > >
> > > > static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > {
> > > > - u32 ctrl, lnkcap, cmd, dev_rev, mask;
> > > > + u32 ctrl, lnkcap, cmd, dev_rev, unmask;
> > > >
> > > > /* Setup PCIe controller to Root Complex mode. */
> > > > ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> > > > @@ -288,10 +292,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > /* Point PCIe unit MBUS decode windows to DRAM space. */
> > > > mvebu_pcie_setup_wins(port);
> > > >
> > > > - /* Enable interrupt lines A-D. */
> > > > - mask = mvebu_readl(port, PCIE_MASK_OFF);
> > > > - mask |= PCIE_MASK_ENABLE_INTS;
> > > > - mvebu_writel(port, mask, PCIE_MASK_OFF);
> > > > + /* Mask all interrupt sources. */
> > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > > > +
> > > > + /* Clear all interrupt causes. */
> > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > > > +
> > > > + if (port->intx_irq <= 0) {
> > > > + /*
> > > > + * When neither "summary" interrupt, nor "intx" interrupt was
> > > > + * specified in DT then unmask all legacy INTx interrupts as in
> > > > + * this case driver does not provide a way for masking and
> > > > + * unmasking of individual legacy INTx interrupts. In this case
> > > > + * all interrupts, including legacy INTx are reported via one
> > > > + * shared GIC source and therefore kernel cannot distinguish
> > > > + * which individual legacy INTx was triggered. These interrupts
> > > > + * are shared, so it should not cause any issue. Just
> > > > + * performance penalty as every PCIe interrupt handler needs to
> > > > + * be called when some interrupt is triggered.
> > > > + */
> > >
> > > This comment applies to current mainline right (ie it describes how
> > > current mainline handles INTx) ? IMO you should split it out in a
> > > separate patch.
> >
> > This above comment describe what happens in if-branch when intx_irq is
> > not set (as written in comment "when intx interrupt was not specified in
> > DT"). You are right that this is also the behavior in the current
> > mainline.
> >
> > I'm not sure if this comment can be split out as support for "intx"
> > interrupt is in this patch.
> >
> > > I understand it is hard but a patch is a logical _change_, this
> > > comment is a change per se, it is a clarification on current
> > > behaviour.
> >
> > Ok, I could try to split this comment into two patches, but part about
> > if-branch comment needs to stay in "this" patch.
>
> I have done it locally.
>
> Let me know when I should resend this patch series and I will include
> into it also these changes.
Hi,
yes please resend it and I will merge it.
Thanks,
Lorenzo
On Tuesday 22 February 2022 10:21:06 Lorenzo Pieralisi wrote:
> On Thu, Feb 17, 2022 at 12:40:39AM +0100, Pali Rohár wrote:
> > On Friday 11 February 2022 18:52:02 Pali Rohár wrote:
> > > On Friday 11 February 2022 17:19:17 Lorenzo Pieralisi wrote:
> > > > On Wed, Jan 12, 2022 at 04:18:13PM +0100, Pali Rohár wrote:
> > > > > This adds support for legacy INTx interrupts received from other PCIe
> > > > > devices and which are reported by a new INTx irq chip.
> > > > >
> > > > > With this change, kernel can distinguish between INTA, INTB, INTC and INTD
> > > > > interrupts.
> > > > >
> > > > > Note that for this support, device tree files has to be properly adjusted
> > > > > to provide "interrupts" or "interrupts-extended" property with intx
> > > > > interrupt source, "interrupt-names" property with "intx" string and also
> > > > > 'interrupt-controller' subnode must be defined.
> > > > >
> > > > > If device tree files do not provide these nodes then driver would work as
> > > > > before.
> > > >
> > > > Nit: this information is not useful. DT rules are written in DT
> > > > bindings, not in kernel commit logs. All I am saying is that firmware
> > > > developers should not have to read this log to write firmware.
> > >
> > > It was not intended for firmware developers, but for reviewers of this
> > > patch to understand, what is happening in code and that with old DT
> > > files this patch does not change driver behavior (= work as before).
> > >
> > > > > Signed-off-by: Pali Rohár <[email protected]>
> > > > > ---
> > > > > drivers/pci/controller/pci-mvebu.c | 185 +++++++++++++++++++++++++++--
> > > > > 1 file changed, 177 insertions(+), 8 deletions(-)
> > > > >
> > > > > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > > > > index 1e90ab888075..dbb6ecb4cb70 100644
> > > > > --- a/drivers/pci/controller/pci-mvebu.c
> > > > > +++ b/drivers/pci/controller/pci-mvebu.c
> > > > > @@ -54,9 +54,10 @@
> > > > > PCIE_CONF_ADDR_EN)
> > > > > #define PCIE_CONF_DATA_OFF 0x18fc
> > > > > #define PCIE_INT_CAUSE_OFF 0x1900
> > > > > +#define PCIE_INT_UNMASK_OFF 0x1910
> > > >
> > > > Nit: I understand it is tempting but here you are redefining or better
> > > > giving a proper label to a register. Separate patch please.
> > >
> > > Ok!
> > >
> > > > > +#define PCIE_INT_INTX(i) BIT(24+i)
> > > > > #define PCIE_INT_PM_PME BIT(28)
> > > > > -#define PCIE_MASK_OFF 0x1910
> > > >
> > > > See above.
> > > >
> > > > > -#define PCIE_MASK_ENABLE_INTS 0x0f000000
> > > > > +#define PCIE_INT_ALL_MASK GENMASK(31, 0)
> > > > > #define PCIE_CTRL_OFF 0x1a00
> > > > > #define PCIE_CTRL_X1_MODE 0x0001
> > > > > #define PCIE_CTRL_RC_MODE BIT(1)
> > > > > @@ -110,6 +111,9 @@ struct mvebu_pcie_port {
> > > > > struct mvebu_pcie_window iowin;
> > > > > u32 saved_pcie_stat;
> > > > > struct resource regs;
> > > > > + struct irq_domain *intx_irq_domain;
> > > > > + raw_spinlock_t irq_lock;
> > > > > + int intx_irq;
> > > > > };
> > > > >
> > > > > static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
> > > > > @@ -235,7 +239,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
> > > > >
> > > > > static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > > {
> > > > > - u32 ctrl, lnkcap, cmd, dev_rev, mask;
> > > > > + u32 ctrl, lnkcap, cmd, dev_rev, unmask;
> > > > >
> > > > > /* Setup PCIe controller to Root Complex mode. */
> > > > > ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> > > > > @@ -288,10 +292,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > > /* Point PCIe unit MBUS decode windows to DRAM space. */
> > > > > mvebu_pcie_setup_wins(port);
> > > > >
> > > > > - /* Enable interrupt lines A-D. */
> > > > > - mask = mvebu_readl(port, PCIE_MASK_OFF);
> > > > > - mask |= PCIE_MASK_ENABLE_INTS;
> > > > > - mvebu_writel(port, mask, PCIE_MASK_OFF);
> > > > > + /* Mask all interrupt sources. */
> > > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > > > > +
> > > > > + /* Clear all interrupt causes. */
> > > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > > > > +
> > > > > + if (port->intx_irq <= 0) {
> > > > > + /*
> > > > > + * When neither "summary" interrupt, nor "intx" interrupt was
> > > > > + * specified in DT then unmask all legacy INTx interrupts as in
> > > > > + * this case driver does not provide a way for masking and
> > > > > + * unmasking of individual legacy INTx interrupts. In this case
> > > > > + * all interrupts, including legacy INTx are reported via one
> > > > > + * shared GIC source and therefore kernel cannot distinguish
> > > > > + * which individual legacy INTx was triggered. These interrupts
> > > > > + * are shared, so it should not cause any issue. Just
> > > > > + * performance penalty as every PCIe interrupt handler needs to
> > > > > + * be called when some interrupt is triggered.
> > > > > + */
> > > >
> > > > This comment applies to current mainline right (ie it describes how
> > > > current mainline handles INTx) ? IMO you should split it out in a
> > > > separate patch.
> > >
> > > This above comment describe what happens in if-branch when intx_irq is
> > > not set (as written in comment "when intx interrupt was not specified in
> > > DT"). You are right that this is also the behavior in the current
> > > mainline.
> > >
> > > I'm not sure if this comment can be split out as support for "intx"
> > > interrupt is in this patch.
> > >
> > > > I understand it is hard but a patch is a logical _change_, this
> > > > comment is a change per se, it is a clarification on current
> > > > behaviour.
> > >
> > > Ok, I could try to split this comment into two patches, but part about
> > > if-branch comment needs to stay in "this" patch.
> >
> > I have done it locally.
> >
> > Let me know when I should resend this patch series and I will include
> > into it also these changes.
>
> Hi,
>
> yes please resend it and I will merge it.
Done!
https://lore.kernel.org/linux-pci/[email protected]/T/#u
>
> Thanks,
> Lorenzo
On Tue, Feb 22, 2022 at 11:51:29AM +0100, Pali Roh?r wrote:
> On Tuesday 22 February 2022 10:21:06 Lorenzo Pieralisi wrote:
> > On Thu, Feb 17, 2022 at 12:40:39AM +0100, Pali Roh?r wrote:
> > > On Friday 11 February 2022 18:52:02 Pali Roh?r wrote:
> > > > On Friday 11 February 2022 17:19:17 Lorenzo Pieralisi wrote:
> > > > > On Wed, Jan 12, 2022 at 04:18:13PM +0100, Pali Roh?r wrote:
> > > > > > This adds support for legacy INTx interrupts received from other PCIe
> > > > > > devices and which are reported by a new INTx irq chip.
> > > > > >
> > > > > > With this change, kernel can distinguish between INTA, INTB, INTC and INTD
> > > > > > interrupts.
> > > > > >
> > > > > > Note that for this support, device tree files has to be properly adjusted
> > > > > > to provide "interrupts" or "interrupts-extended" property with intx
> > > > > > interrupt source, "interrupt-names" property with "intx" string and also
> > > > > > 'interrupt-controller' subnode must be defined.
> > > > > >
> > > > > > If device tree files do not provide these nodes then driver would work as
> > > > > > before.
> > > > >
> > > > > Nit: this information is not useful. DT rules are written in DT
> > > > > bindings, not in kernel commit logs. All I am saying is that firmware
> > > > > developers should not have to read this log to write firmware.
> > > >
> > > > It was not intended for firmware developers, but for reviewers of this
> > > > patch to understand, what is happening in code and that with old DT
> > > > files this patch does not change driver behavior (= work as before).
> > > >
> > > > > > Signed-off-by: Pali Roh?r <[email protected]>
> > > > > > ---
> > > > > > drivers/pci/controller/pci-mvebu.c | 185 +++++++++++++++++++++++++++--
> > > > > > 1 file changed, 177 insertions(+), 8 deletions(-)
> > > > > >
> > > > > > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > > > > > index 1e90ab888075..dbb6ecb4cb70 100644
> > > > > > --- a/drivers/pci/controller/pci-mvebu.c
> > > > > > +++ b/drivers/pci/controller/pci-mvebu.c
> > > > > > @@ -54,9 +54,10 @@
> > > > > > PCIE_CONF_ADDR_EN)
> > > > > > #define PCIE_CONF_DATA_OFF 0x18fc
> > > > > > #define PCIE_INT_CAUSE_OFF 0x1900
> > > > > > +#define PCIE_INT_UNMASK_OFF 0x1910
> > > > >
> > > > > Nit: I understand it is tempting but here you are redefining or better
> > > > > giving a proper label to a register. Separate patch please.
> > > >
> > > > Ok!
> > > >
> > > > > > +#define PCIE_INT_INTX(i) BIT(24+i)
> > > > > > #define PCIE_INT_PM_PME BIT(28)
> > > > > > -#define PCIE_MASK_OFF 0x1910
> > > > >
> > > > > See above.
> > > > >
> > > > > > -#define PCIE_MASK_ENABLE_INTS 0x0f000000
> > > > > > +#define PCIE_INT_ALL_MASK GENMASK(31, 0)
> > > > > > #define PCIE_CTRL_OFF 0x1a00
> > > > > > #define PCIE_CTRL_X1_MODE 0x0001
> > > > > > #define PCIE_CTRL_RC_MODE BIT(1)
> > > > > > @@ -110,6 +111,9 @@ struct mvebu_pcie_port {
> > > > > > struct mvebu_pcie_window iowin;
> > > > > > u32 saved_pcie_stat;
> > > > > > struct resource regs;
> > > > > > + struct irq_domain *intx_irq_domain;
> > > > > > + raw_spinlock_t irq_lock;
> > > > > > + int intx_irq;
> > > > > > };
> > > > > >
> > > > > > static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
> > > > > > @@ -235,7 +239,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
> > > > > >
> > > > > > static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > > > {
> > > > > > - u32 ctrl, lnkcap, cmd, dev_rev, mask;
> > > > > > + u32 ctrl, lnkcap, cmd, dev_rev, unmask;
> > > > > >
> > > > > > /* Setup PCIe controller to Root Complex mode. */
> > > > > > ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> > > > > > @@ -288,10 +292,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > > > /* Point PCIe unit MBUS decode windows to DRAM space. */
> > > > > > mvebu_pcie_setup_wins(port);
> > > > > >
> > > > > > - /* Enable interrupt lines A-D. */
> > > > > > - mask = mvebu_readl(port, PCIE_MASK_OFF);
> > > > > > - mask |= PCIE_MASK_ENABLE_INTS;
> > > > > > - mvebu_writel(port, mask, PCIE_MASK_OFF);
> > > > > > + /* Mask all interrupt sources. */
> > > > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > > > > > +
> > > > > > + /* Clear all interrupt causes. */
> > > > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > > > > > +
> > > > > > + if (port->intx_irq <= 0) {
> > > > > > + /*
> > > > > > + * When neither "summary" interrupt, nor "intx" interrupt was
> > > > > > + * specified in DT then unmask all legacy INTx interrupts as in
> > > > > > + * this case driver does not provide a way for masking and
> > > > > > + * unmasking of individual legacy INTx interrupts. In this case
> > > > > > + * all interrupts, including legacy INTx are reported via one
> > > > > > + * shared GIC source and therefore kernel cannot distinguish
> > > > > > + * which individual legacy INTx was triggered. These interrupts
> > > > > > + * are shared, so it should not cause any issue. Just
> > > > > > + * performance penalty as every PCIe interrupt handler needs to
> > > > > > + * be called when some interrupt is triggered.
> > > > > > + */
> > > > >
> > > > > This comment applies to current mainline right (ie it describes how
> > > > > current mainline handles INTx) ? IMO you should split it out in a
> > > > > separate patch.
> > > >
> > > > This above comment describe what happens in if-branch when intx_irq is
> > > > not set (as written in comment "when intx interrupt was not specified in
> > > > DT"). You are right that this is also the behavior in the current
> > > > mainline.
> > > >
> > > > I'm not sure if this comment can be split out as support for "intx"
> > > > interrupt is in this patch.
> > > >
> > > > > I understand it is hard but a patch is a logical _change_, this
> > > > > comment is a change per se, it is a clarification on current
> > > > > behaviour.
> > > >
> > > > Ok, I could try to split this comment into two patches, but part about
> > > > if-branch comment needs to stay in "this" patch.
> > >
> > > I have done it locally.
> > >
> > > Let me know when I should resend this patch series and I will include
> > > into it also these changes.
> >
> > Hi,
> >
> > yes please resend it and I will merge it.
>
> Done!
> https://lore.kernel.org/linux-pci/[email protected]/T/#u
Can you rebase it please on top of my pci/mvebu branch ?
https://git.kernel.org/pub/scm/linux/kernel/git/lpieralisi/pci.git/
Forgive me, I forgot to mention that, thanks.
Lorenzo
On Tuesday 22 February 2022 15:45:53 Lorenzo Pieralisi wrote:
> On Tue, Feb 22, 2022 at 04:42:26PM +0100, Pali Rohár wrote:
> > On Tuesday 22 February 2022 15:24:09 Lorenzo Pieralisi wrote:
> > > On Tue, Feb 22, 2022 at 11:51:29AM +0100, Pali Rohár wrote:
> > > > On Tuesday 22 February 2022 10:21:06 Lorenzo Pieralisi wrote:
> > > > > On Thu, Feb 17, 2022 at 12:40:39AM +0100, Pali Rohár wrote:
> > > > > > On Friday 11 February 2022 18:52:02 Pali Rohár wrote:
> > > > > > > On Friday 11 February 2022 17:19:17 Lorenzo Pieralisi wrote:
> > > > > > > > On Wed, Jan 12, 2022 at 04:18:13PM +0100, Pali Rohár wrote:
> > > > > > > > > This adds support for legacy INTx interrupts received from other PCIe
> > > > > > > > > devices and which are reported by a new INTx irq chip.
> > > > > > > > >
> > > > > > > > > With this change, kernel can distinguish between INTA, INTB, INTC and INTD
> > > > > > > > > interrupts.
> > > > > > > > >
> > > > > > > > > Note that for this support, device tree files has to be properly adjusted
> > > > > > > > > to provide "interrupts" or "interrupts-extended" property with intx
> > > > > > > > > interrupt source, "interrupt-names" property with "intx" string and also
> > > > > > > > > 'interrupt-controller' subnode must be defined.
> > > > > > > > >
> > > > > > > > > If device tree files do not provide these nodes then driver would work as
> > > > > > > > > before.
> > > > > > > >
> > > > > > > > Nit: this information is not useful. DT rules are written in DT
> > > > > > > > bindings, not in kernel commit logs. All I am saying is that firmware
> > > > > > > > developers should not have to read this log to write firmware.
> > > > > > >
> > > > > > > It was not intended for firmware developers, but for reviewers of this
> > > > > > > patch to understand, what is happening in code and that with old DT
> > > > > > > files this patch does not change driver behavior (= work as before).
> > > > > > >
> > > > > > > > > Signed-off-by: Pali Rohár <[email protected]>
> > > > > > > > > ---
> > > > > > > > > drivers/pci/controller/pci-mvebu.c | 185 +++++++++++++++++++++++++++--
> > > > > > > > > 1 file changed, 177 insertions(+), 8 deletions(-)
> > > > > > > > >
> > > > > > > > > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > > > > > > > > index 1e90ab888075..dbb6ecb4cb70 100644
> > > > > > > > > --- a/drivers/pci/controller/pci-mvebu.c
> > > > > > > > > +++ b/drivers/pci/controller/pci-mvebu.c
> > > > > > > > > @@ -54,9 +54,10 @@
> > > > > > > > > PCIE_CONF_ADDR_EN)
> > > > > > > > > #define PCIE_CONF_DATA_OFF 0x18fc
> > > > > > > > > #define PCIE_INT_CAUSE_OFF 0x1900
> > > > > > > > > +#define PCIE_INT_UNMASK_OFF 0x1910
> > > > > > > >
> > > > > > > > Nit: I understand it is tempting but here you are redefining or better
> > > > > > > > giving a proper label to a register. Separate patch please.
> > > > > > >
> > > > > > > Ok!
> > > > > > >
> > > > > > > > > +#define PCIE_INT_INTX(i) BIT(24+i)
> > > > > > > > > #define PCIE_INT_PM_PME BIT(28)
> > > > > > > > > -#define PCIE_MASK_OFF 0x1910
> > > > > > > >
> > > > > > > > See above.
> > > > > > > >
> > > > > > > > > -#define PCIE_MASK_ENABLE_INTS 0x0f000000
> > > > > > > > > +#define PCIE_INT_ALL_MASK GENMASK(31, 0)
> > > > > > > > > #define PCIE_CTRL_OFF 0x1a00
> > > > > > > > > #define PCIE_CTRL_X1_MODE 0x0001
> > > > > > > > > #define PCIE_CTRL_RC_MODE BIT(1)
> > > > > > > > > @@ -110,6 +111,9 @@ struct mvebu_pcie_port {
> > > > > > > > > struct mvebu_pcie_window iowin;
> > > > > > > > > u32 saved_pcie_stat;
> > > > > > > > > struct resource regs;
> > > > > > > > > + struct irq_domain *intx_irq_domain;
> > > > > > > > > + raw_spinlock_t irq_lock;
> > > > > > > > > + int intx_irq;
> > > > > > > > > };
> > > > > > > > >
> > > > > > > > > static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
> > > > > > > > > @@ -235,7 +239,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
> > > > > > > > >
> > > > > > > > > static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > > > > > > {
> > > > > > > > > - u32 ctrl, lnkcap, cmd, dev_rev, mask;
> > > > > > > > > + u32 ctrl, lnkcap, cmd, dev_rev, unmask;
> > > > > > > > >
> > > > > > > > > /* Setup PCIe controller to Root Complex mode. */
> > > > > > > > > ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> > > > > > > > > @@ -288,10 +292,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > > > > > > /* Point PCIe unit MBUS decode windows to DRAM space. */
> > > > > > > > > mvebu_pcie_setup_wins(port);
> > > > > > > > >
> > > > > > > > > - /* Enable interrupt lines A-D. */
> > > > > > > > > - mask = mvebu_readl(port, PCIE_MASK_OFF);
> > > > > > > > > - mask |= PCIE_MASK_ENABLE_INTS;
> > > > > > > > > - mvebu_writel(port, mask, PCIE_MASK_OFF);
> > > > > > > > > + /* Mask all interrupt sources. */
> > > > > > > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > > > > > > > > +
> > > > > > > > > + /* Clear all interrupt causes. */
> > > > > > > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > > > > > > > > +
> > > > > > > > > + if (port->intx_irq <= 0) {
> > > > > > > > > + /*
> > > > > > > > > + * When neither "summary" interrupt, nor "intx" interrupt was
> > > > > > > > > + * specified in DT then unmask all legacy INTx interrupts as in
> > > > > > > > > + * this case driver does not provide a way for masking and
> > > > > > > > > + * unmasking of individual legacy INTx interrupts. In this case
> > > > > > > > > + * all interrupts, including legacy INTx are reported via one
> > > > > > > > > + * shared GIC source and therefore kernel cannot distinguish
> > > > > > > > > + * which individual legacy INTx was triggered. These interrupts
> > > > > > > > > + * are shared, so it should not cause any issue. Just
> > > > > > > > > + * performance penalty as every PCIe interrupt handler needs to
> > > > > > > > > + * be called when some interrupt is triggered.
> > > > > > > > > + */
> > > > > > > >
> > > > > > > > This comment applies to current mainline right (ie it describes how
> > > > > > > > current mainline handles INTx) ? IMO you should split it out in a
> > > > > > > > separate patch.
> > > > > > >
> > > > > > > This above comment describe what happens in if-branch when intx_irq is
> > > > > > > not set (as written in comment "when intx interrupt was not specified in
> > > > > > > DT"). You are right that this is also the behavior in the current
> > > > > > > mainline.
> > > > > > >
> > > > > > > I'm not sure if this comment can be split out as support for "intx"
> > > > > > > interrupt is in this patch.
> > > > > > >
> > > > > > > > I understand it is hard but a patch is a logical _change_, this
> > > > > > > > comment is a change per se, it is a clarification on current
> > > > > > > > behaviour.
> > > > > > >
> > > > > > > Ok, I could try to split this comment into two patches, but part about
> > > > > > > if-branch comment needs to stay in "this" patch.
> > > > > >
> > > > > > I have done it locally.
> > > > > >
> > > > > > Let me know when I should resend this patch series and I will include
> > > > > > into it also these changes.
> > > > >
> > > > > Hi,
> > > > >
> > > > > yes please resend it and I will merge it.
> > > >
> > > > Done!
> > > > https://lore.kernel.org/linux-pci/[email protected]/T/#u
> > >
> > > Can you rebase it please on top of my pci/mvebu branch ?
> > >
> > > https://git.kernel.org/pub/scm/linux/kernel/git/lpieralisi/pci.git/
> > >
> > > Forgive me, I forgot to mention that, thanks.
> > >
> > > Lorenzo
> >
> > Ok! I rebased V3 on top of c3bd7dc553eea5a3595ca3aa0adee9bf83622a1f
> > (pci/mvebu branch in your repo), fixed conflicts and pushed to my git
> > repo https://git.kernel.org/pub/scm/linux/kernel/git/pali/linux.git/
> > as commit 42402f0cfc362ffb0b7e464f420d6ead342dab2b (lpieralisi-pci-mvebu
> > branch). It is enough? Or do you want me to resend it via emails?
>
> You need to re-send it to linux-pci ML, I only apply patches posted
> there, sorry for the churn, thanks.
>
> Lorenzo
Done!
https://lore.kernel.org/linux-pci/[email protected]/T/#u
On Tue, Feb 22, 2022 at 04:42:26PM +0100, Pali Roh?r wrote:
> On Tuesday 22 February 2022 15:24:09 Lorenzo Pieralisi wrote:
> > On Tue, Feb 22, 2022 at 11:51:29AM +0100, Pali Roh?r wrote:
> > > On Tuesday 22 February 2022 10:21:06 Lorenzo Pieralisi wrote:
> > > > On Thu, Feb 17, 2022 at 12:40:39AM +0100, Pali Roh?r wrote:
> > > > > On Friday 11 February 2022 18:52:02 Pali Roh?r wrote:
> > > > > > On Friday 11 February 2022 17:19:17 Lorenzo Pieralisi wrote:
> > > > > > > On Wed, Jan 12, 2022 at 04:18:13PM +0100, Pali Roh?r wrote:
> > > > > > > > This adds support for legacy INTx interrupts received from other PCIe
> > > > > > > > devices and which are reported by a new INTx irq chip.
> > > > > > > >
> > > > > > > > With this change, kernel can distinguish between INTA, INTB, INTC and INTD
> > > > > > > > interrupts.
> > > > > > > >
> > > > > > > > Note that for this support, device tree files has to be properly adjusted
> > > > > > > > to provide "interrupts" or "interrupts-extended" property with intx
> > > > > > > > interrupt source, "interrupt-names" property with "intx" string and also
> > > > > > > > 'interrupt-controller' subnode must be defined.
> > > > > > > >
> > > > > > > > If device tree files do not provide these nodes then driver would work as
> > > > > > > > before.
> > > > > > >
> > > > > > > Nit: this information is not useful. DT rules are written in DT
> > > > > > > bindings, not in kernel commit logs. All I am saying is that firmware
> > > > > > > developers should not have to read this log to write firmware.
> > > > > >
> > > > > > It was not intended for firmware developers, but for reviewers of this
> > > > > > patch to understand, what is happening in code and that with old DT
> > > > > > files this patch does not change driver behavior (= work as before).
> > > > > >
> > > > > > > > Signed-off-by: Pali Roh?r <[email protected]>
> > > > > > > > ---
> > > > > > > > drivers/pci/controller/pci-mvebu.c | 185 +++++++++++++++++++++++++++--
> > > > > > > > 1 file changed, 177 insertions(+), 8 deletions(-)
> > > > > > > >
> > > > > > > > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > > > > > > > index 1e90ab888075..dbb6ecb4cb70 100644
> > > > > > > > --- a/drivers/pci/controller/pci-mvebu.c
> > > > > > > > +++ b/drivers/pci/controller/pci-mvebu.c
> > > > > > > > @@ -54,9 +54,10 @@
> > > > > > > > PCIE_CONF_ADDR_EN)
> > > > > > > > #define PCIE_CONF_DATA_OFF 0x18fc
> > > > > > > > #define PCIE_INT_CAUSE_OFF 0x1900
> > > > > > > > +#define PCIE_INT_UNMASK_OFF 0x1910
> > > > > > >
> > > > > > > Nit: I understand it is tempting but here you are redefining or better
> > > > > > > giving a proper label to a register. Separate patch please.
> > > > > >
> > > > > > Ok!
> > > > > >
> > > > > > > > +#define PCIE_INT_INTX(i) BIT(24+i)
> > > > > > > > #define PCIE_INT_PM_PME BIT(28)
> > > > > > > > -#define PCIE_MASK_OFF 0x1910
> > > > > > >
> > > > > > > See above.
> > > > > > >
> > > > > > > > -#define PCIE_MASK_ENABLE_INTS 0x0f000000
> > > > > > > > +#define PCIE_INT_ALL_MASK GENMASK(31, 0)
> > > > > > > > #define PCIE_CTRL_OFF 0x1a00
> > > > > > > > #define PCIE_CTRL_X1_MODE 0x0001
> > > > > > > > #define PCIE_CTRL_RC_MODE BIT(1)
> > > > > > > > @@ -110,6 +111,9 @@ struct mvebu_pcie_port {
> > > > > > > > struct mvebu_pcie_window iowin;
> > > > > > > > u32 saved_pcie_stat;
> > > > > > > > struct resource regs;
> > > > > > > > + struct irq_domain *intx_irq_domain;
> > > > > > > > + raw_spinlock_t irq_lock;
> > > > > > > > + int intx_irq;
> > > > > > > > };
> > > > > > > >
> > > > > > > > static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
> > > > > > > > @@ -235,7 +239,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
> > > > > > > >
> > > > > > > > static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > > > > > {
> > > > > > > > - u32 ctrl, lnkcap, cmd, dev_rev, mask;
> > > > > > > > + u32 ctrl, lnkcap, cmd, dev_rev, unmask;
> > > > > > > >
> > > > > > > > /* Setup PCIe controller to Root Complex mode. */
> > > > > > > > ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> > > > > > > > @@ -288,10 +292,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > > > > > /* Point PCIe unit MBUS decode windows to DRAM space. */
> > > > > > > > mvebu_pcie_setup_wins(port);
> > > > > > > >
> > > > > > > > - /* Enable interrupt lines A-D. */
> > > > > > > > - mask = mvebu_readl(port, PCIE_MASK_OFF);
> > > > > > > > - mask |= PCIE_MASK_ENABLE_INTS;
> > > > > > > > - mvebu_writel(port, mask, PCIE_MASK_OFF);
> > > > > > > > + /* Mask all interrupt sources. */
> > > > > > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > > > > > > > +
> > > > > > > > + /* Clear all interrupt causes. */
> > > > > > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > > > > > > > +
> > > > > > > > + if (port->intx_irq <= 0) {
> > > > > > > > + /*
> > > > > > > > + * When neither "summary" interrupt, nor "intx" interrupt was
> > > > > > > > + * specified in DT then unmask all legacy INTx interrupts as in
> > > > > > > > + * this case driver does not provide a way for masking and
> > > > > > > > + * unmasking of individual legacy INTx interrupts. In this case
> > > > > > > > + * all interrupts, including legacy INTx are reported via one
> > > > > > > > + * shared GIC source and therefore kernel cannot distinguish
> > > > > > > > + * which individual legacy INTx was triggered. These interrupts
> > > > > > > > + * are shared, so it should not cause any issue. Just
> > > > > > > > + * performance penalty as every PCIe interrupt handler needs to
> > > > > > > > + * be called when some interrupt is triggered.
> > > > > > > > + */
> > > > > > >
> > > > > > > This comment applies to current mainline right (ie it describes how
> > > > > > > current mainline handles INTx) ? IMO you should split it out in a
> > > > > > > separate patch.
> > > > > >
> > > > > > This above comment describe what happens in if-branch when intx_irq is
> > > > > > not set (as written in comment "when intx interrupt was not specified in
> > > > > > DT"). You are right that this is also the behavior in the current
> > > > > > mainline.
> > > > > >
> > > > > > I'm not sure if this comment can be split out as support for "intx"
> > > > > > interrupt is in this patch.
> > > > > >
> > > > > > > I understand it is hard but a patch is a logical _change_, this
> > > > > > > comment is a change per se, it is a clarification on current
> > > > > > > behaviour.
> > > > > >
> > > > > > Ok, I could try to split this comment into two patches, but part about
> > > > > > if-branch comment needs to stay in "this" patch.
> > > > >
> > > > > I have done it locally.
> > > > >
> > > > > Let me know when I should resend this patch series and I will include
> > > > > into it also these changes.
> > > >
> > > > Hi,
> > > >
> > > > yes please resend it and I will merge it.
> > >
> > > Done!
> > > https://lore.kernel.org/linux-pci/[email protected]/T/#u
> >
> > Can you rebase it please on top of my pci/mvebu branch ?
> >
> > https://git.kernel.org/pub/scm/linux/kernel/git/lpieralisi/pci.git/
> >
> > Forgive me, I forgot to mention that, thanks.
> >
> > Lorenzo
>
> Ok! I rebased V3 on top of c3bd7dc553eea5a3595ca3aa0adee9bf83622a1f
> (pci/mvebu branch in your repo), fixed conflicts and pushed to my git
> repo https://git.kernel.org/pub/scm/linux/kernel/git/pali/linux.git/
> as commit 42402f0cfc362ffb0b7e464f420d6ead342dab2b (lpieralisi-pci-mvebu
> branch). It is enough? Or do you want me to resend it via emails?
You need to re-send it to linux-pci ML, I only apply patches posted
there, sorry for the churn, thanks.
Lorenzo
On Tuesday 22 February 2022 15:24:09 Lorenzo Pieralisi wrote:
> On Tue, Feb 22, 2022 at 11:51:29AM +0100, Pali Rohár wrote:
> > On Tuesday 22 February 2022 10:21:06 Lorenzo Pieralisi wrote:
> > > On Thu, Feb 17, 2022 at 12:40:39AM +0100, Pali Rohár wrote:
> > > > On Friday 11 February 2022 18:52:02 Pali Rohár wrote:
> > > > > On Friday 11 February 2022 17:19:17 Lorenzo Pieralisi wrote:
> > > > > > On Wed, Jan 12, 2022 at 04:18:13PM +0100, Pali Rohár wrote:
> > > > > > > This adds support for legacy INTx interrupts received from other PCIe
> > > > > > > devices and which are reported by a new INTx irq chip.
> > > > > > >
> > > > > > > With this change, kernel can distinguish between INTA, INTB, INTC and INTD
> > > > > > > interrupts.
> > > > > > >
> > > > > > > Note that for this support, device tree files has to be properly adjusted
> > > > > > > to provide "interrupts" or "interrupts-extended" property with intx
> > > > > > > interrupt source, "interrupt-names" property with "intx" string and also
> > > > > > > 'interrupt-controller' subnode must be defined.
> > > > > > >
> > > > > > > If device tree files do not provide these nodes then driver would work as
> > > > > > > before.
> > > > > >
> > > > > > Nit: this information is not useful. DT rules are written in DT
> > > > > > bindings, not in kernel commit logs. All I am saying is that firmware
> > > > > > developers should not have to read this log to write firmware.
> > > > >
> > > > > It was not intended for firmware developers, but for reviewers of this
> > > > > patch to understand, what is happening in code and that with old DT
> > > > > files this patch does not change driver behavior (= work as before).
> > > > >
> > > > > > > Signed-off-by: Pali Rohár <[email protected]>
> > > > > > > ---
> > > > > > > drivers/pci/controller/pci-mvebu.c | 185 +++++++++++++++++++++++++++--
> > > > > > > 1 file changed, 177 insertions(+), 8 deletions(-)
> > > > > > >
> > > > > > > diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
> > > > > > > index 1e90ab888075..dbb6ecb4cb70 100644
> > > > > > > --- a/drivers/pci/controller/pci-mvebu.c
> > > > > > > +++ b/drivers/pci/controller/pci-mvebu.c
> > > > > > > @@ -54,9 +54,10 @@
> > > > > > > PCIE_CONF_ADDR_EN)
> > > > > > > #define PCIE_CONF_DATA_OFF 0x18fc
> > > > > > > #define PCIE_INT_CAUSE_OFF 0x1900
> > > > > > > +#define PCIE_INT_UNMASK_OFF 0x1910
> > > > > >
> > > > > > Nit: I understand it is tempting but here you are redefining or better
> > > > > > giving a proper label to a register. Separate patch please.
> > > > >
> > > > > Ok!
> > > > >
> > > > > > > +#define PCIE_INT_INTX(i) BIT(24+i)
> > > > > > > #define PCIE_INT_PM_PME BIT(28)
> > > > > > > -#define PCIE_MASK_OFF 0x1910
> > > > > >
> > > > > > See above.
> > > > > >
> > > > > > > -#define PCIE_MASK_ENABLE_INTS 0x0f000000
> > > > > > > +#define PCIE_INT_ALL_MASK GENMASK(31, 0)
> > > > > > > #define PCIE_CTRL_OFF 0x1a00
> > > > > > > #define PCIE_CTRL_X1_MODE 0x0001
> > > > > > > #define PCIE_CTRL_RC_MODE BIT(1)
> > > > > > > @@ -110,6 +111,9 @@ struct mvebu_pcie_port {
> > > > > > > struct mvebu_pcie_window iowin;
> > > > > > > u32 saved_pcie_stat;
> > > > > > > struct resource regs;
> > > > > > > + struct irq_domain *intx_irq_domain;
> > > > > > > + raw_spinlock_t irq_lock;
> > > > > > > + int intx_irq;
> > > > > > > };
> > > > > > >
> > > > > > > static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
> > > > > > > @@ -235,7 +239,7 @@ static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
> > > > > > >
> > > > > > > static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > > > > {
> > > > > > > - u32 ctrl, lnkcap, cmd, dev_rev, mask;
> > > > > > > + u32 ctrl, lnkcap, cmd, dev_rev, unmask;
> > > > > > >
> > > > > > > /* Setup PCIe controller to Root Complex mode. */
> > > > > > > ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
> > > > > > > @@ -288,10 +292,30 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
> > > > > > > /* Point PCIe unit MBUS decode windows to DRAM space. */
> > > > > > > mvebu_pcie_setup_wins(port);
> > > > > > >
> > > > > > > - /* Enable interrupt lines A-D. */
> > > > > > > - mask = mvebu_readl(port, PCIE_MASK_OFF);
> > > > > > > - mask |= PCIE_MASK_ENABLE_INTS;
> > > > > > > - mvebu_writel(port, mask, PCIE_MASK_OFF);
> > > > > > > + /* Mask all interrupt sources. */
> > > > > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
> > > > > > > +
> > > > > > > + /* Clear all interrupt causes. */
> > > > > > > + mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
> > > > > > > +
> > > > > > > + if (port->intx_irq <= 0) {
> > > > > > > + /*
> > > > > > > + * When neither "summary" interrupt, nor "intx" interrupt was
> > > > > > > + * specified in DT then unmask all legacy INTx interrupts as in
> > > > > > > + * this case driver does not provide a way for masking and
> > > > > > > + * unmasking of individual legacy INTx interrupts. In this case
> > > > > > > + * all interrupts, including legacy INTx are reported via one
> > > > > > > + * shared GIC source and therefore kernel cannot distinguish
> > > > > > > + * which individual legacy INTx was triggered. These interrupts
> > > > > > > + * are shared, so it should not cause any issue. Just
> > > > > > > + * performance penalty as every PCIe interrupt handler needs to
> > > > > > > + * be called when some interrupt is triggered.
> > > > > > > + */
> > > > > >
> > > > > > This comment applies to current mainline right (ie it describes how
> > > > > > current mainline handles INTx) ? IMO you should split it out in a
> > > > > > separate patch.
> > > > >
> > > > > This above comment describe what happens in if-branch when intx_irq is
> > > > > not set (as written in comment "when intx interrupt was not specified in
> > > > > DT"). You are right that this is also the behavior in the current
> > > > > mainline.
> > > > >
> > > > > I'm not sure if this comment can be split out as support for "intx"
> > > > > interrupt is in this patch.
> > > > >
> > > > > > I understand it is hard but a patch is a logical _change_, this
> > > > > > comment is a change per se, it is a clarification on current
> > > > > > behaviour.
> > > > >
> > > > > Ok, I could try to split this comment into two patches, but part about
> > > > > if-branch comment needs to stay in "this" patch.
> > > >
> > > > I have done it locally.
> > > >
> > > > Let me know when I should resend this patch series and I will include
> > > > into it also these changes.
> > >
> > > Hi,
> > >
> > > yes please resend it and I will merge it.
> >
> > Done!
> > https://lore.kernel.org/linux-pci/[email protected]/T/#u
>
> Can you rebase it please on top of my pci/mvebu branch ?
>
> https://git.kernel.org/pub/scm/linux/kernel/git/lpieralisi/pci.git/
>
> Forgive me, I forgot to mention that, thanks.
>
> Lorenzo
Ok! I rebased V3 on top of c3bd7dc553eea5a3595ca3aa0adee9bf83622a1f
(pci/mvebu branch in your repo), fixed conflicts and pushed to my git
repo https://git.kernel.org/pub/scm/linux/kernel/git/pali/linux.git/
as commit 42402f0cfc362ffb0b7e464f420d6ead342dab2b (lpieralisi-pci-mvebu
branch). It is enough? Or do you want me to resend it via emails?