[v7,5/7] PCI: mediatek-gen3: Add MSI support

Jianjun Wang jianjun.wang at mediatek.com
Wed Jan 27 08:17:59 EST 2021


On Wed, 2021-01-27 at 13:05 +0000, Marc Zyngier wrote:
> On 2021-01-27 12:31, Jianjun Wang wrote:
> > On Tue, 2021-01-26 at 13:57 +0000, Marc Zyngier wrote:
> >> On 2021-01-13 11:39, Jianjun Wang wrote:
> >> > Add MSI support for MediaTek Gen3 PCIe controller.
> >> >
> >> > This PCIe controller supports up to 256 MSI vectors, the MSI hardware
> >> > block diagram is as follows:
> >> >
> >> >                   +-----+
> >> >                   | GIC |
> >> >                   +-----+
> >> >                      ^
> >> >                      |
> >> >                  port->irq
> >> >                      |
> >> >              +-+-+-+-+-+-+-+-+
> >> >              |0|1|2|3|4|5|6|7| (PCIe intc)
> >> >              +-+-+-+-+-+-+-+-+
> >> >               ^ ^           ^
> >> >               | |    ...    |
> >> >       +-------+ +------+    +-----------+
> >> >       |                |                |
> >> > +-+-+---+--+--+  +-+-+---+--+--+  +-+-+---+--+--+
> >> > |0|1|...|30|31|  |0|1|...|30|31|  |0|1|...|30|31| (MSI sets)
> >> > +-+-+---+--+--+  +-+-+---+--+--+  +-+-+---+--+--+
> >> >  ^ ^      ^  ^    ^ ^      ^  ^    ^ ^      ^  ^
> >> >  | |      |  |    | |      |  |    | |      |  |  (MSI vectors)
> >> >  | |      |  |    | |      |  |    | |      |  |
> >> >
> >> >   (MSI SET0)       (MSI SET1)  ...   (MSI SET7)
> >> >
> >> > With 256 MSI vectors supported, the MSI vectors are composed of 8 sets,
> >> > each set has its own address for MSI message, and supports 32 MSI
> >> > vectors
> >> > to generate interrupt.
> >> >
> >> > Signed-off-by: Jianjun Wang <jianjun.wang at mediatek.com>
> >> > Acked-by: Ryder Lee <ryder.lee at mediatek.com>
> >> > ---
> >> >  drivers/pci/controller/pcie-mediatek-gen3.c | 261 ++++++++++++++++++++
> >> >  1 file changed, 261 insertions(+)
> >> >
> >> > diff --git a/drivers/pci/controller/pcie-mediatek-gen3.c
> >> > b/drivers/pci/controller/pcie-mediatek-gen3.c
> >> > index 7979a2856c35..471d97cd1ef9 100644
> >> > --- a/drivers/pci/controller/pcie-mediatek-gen3.c
> >> > +++ b/drivers/pci/controller/pcie-mediatek-gen3.c
> >> > @@ -14,6 +14,7 @@
> >> >  #include <linux/irqdomain.h>
> >> >  #include <linux/kernel.h>
> >> >  #include <linux/module.h>
> >> > +#include <linux/msi.h>
> >> >  #include <linux/of_address.h>
> >> >  #include <linux/of_clk.h>
> >> >  #include <linux/of_pci.h>
> >> > @@ -52,11 +53,28 @@
> >> >  #define PCIE_LINK_STATUS_REG		0x154
> >> >  #define PCIE_PORT_LINKUP		BIT(8)
> >> >
> >> > +#define PCIE_MSI_SET_NUM		8
> >> > +#define PCIE_MSI_IRQS_PER_SET		32
> >> > +#define PCIE_MSI_IRQS_NUM \
> >> > +	(PCIE_MSI_IRQS_PER_SET * (PCIE_MSI_SET_NUM))
> >> 
> >> Spurious inner bracketing.
> >> 
> >> > +
> >> >  #define PCIE_INT_ENABLE_REG		0x180
> >> > +#define PCIE_MSI_ENABLE			GENMASK(PCIE_MSI_SET_NUM + 8 - 1, 8)
> >> > +#define PCIE_MSI_SHIFT			8
> >> >  #define PCIE_INTX_SHIFT			24
> >> >  #define PCIE_INTX_MASK			GENMASK(27, 24)
> >> >
> >> >  #define PCIE_INT_STATUS_REG		0x184
> >> > +#define PCIE_MSI_SET_ENABLE_REG		0x190
> >> > +#define PCIE_MSI_SET_ENABLE		GENMASK(PCIE_MSI_SET_NUM - 1, 0)
> >> > +
> >> > +#define PCIE_MSI_SET_BASE_REG		0xc00
> >> > +#define PCIE_MSI_SET_OFFSET		0x10
> >> > +#define PCIE_MSI_SET_STATUS_OFFSET	0x04
> >> > +#define PCIE_MSI_SET_ENABLE_OFFSET	0x08
> >> > +
> >> > +#define PCIE_MSI_SET_ADDR_HI_BASE	0xc80
> >> > +#define PCIE_MSI_SET_ADDR_HI_OFFSET	0x04
> >> >
> >> >  #define PCIE_TRANS_TABLE_BASE_REG	0x800
> >> >  #define PCIE_ATR_SRC_ADDR_MSB_OFFSET	0x4
> >> > @@ -76,6 +94,18 @@
> >> >  #define PCIE_ATR_TLP_TYPE_MEM		PCIE_ATR_TLP_TYPE(0)
> >> >  #define PCIE_ATR_TLP_TYPE_IO		PCIE_ATR_TLP_TYPE(2)
> >> >
> >> > +/**
> >> > + * struct mtk_pcie_msi - MSI information for each set
> >> > + * @dev: pointer to PCIe device
> >> > + * @base: IO mapped register base
> >> > + * @msg_addr: MSI message address
> >> > + */
> >> > +struct mtk_msi_set {
> >> > +	struct device *dev;
> >> > +	void __iomem *base;
> >> > +	phys_addr_t msg_addr;
> >> > +};
> >> > +
> >> >  /**
> >> >   * struct mtk_pcie_port - PCIe port information
> >> >   * @dev: pointer to PCIe device
> >> > @@ -88,6 +118,11 @@
> >> >   * @num_clks: PCIe clocks count for this port
> >> >   * @irq: PCIe controller interrupt number
> >> >   * @intx_domain: legacy INTx IRQ domain
> >> > + * @msi_domain: MSI IRQ domain
> >> > + * @msi_bottom_domain: MSI IRQ bottom domain
> >> > + * @msi_sets: MSI sets information
> >> > + * @lock: lock protecting IRQ bit map
> >> > + * @msi_irq_in_use: bit map for assigned MSI IRQ
> >> >   */
> >> >  struct mtk_pcie_port {
> >> >  	struct device *dev;
> >> > @@ -101,6 +136,11 @@ struct mtk_pcie_port {
> >> >
> >> >  	int irq;
> >> >  	struct irq_domain *intx_domain;
> >> > +	struct irq_domain *msi_domain;
> >> > +	struct irq_domain *msi_bottom_domain;
> >> > +	struct mtk_msi_set msi_sets[PCIE_MSI_SET_NUM];
> >> > +	struct mutex lock;
> >> > +	DECLARE_BITMAP(msi_irq_in_use, PCIE_MSI_IRQS_NUM);
> >> >  };
> >> >
> >> >  /**
> >> > @@ -243,6 +283,15 @@ static int mtk_pcie_startup_port(struct
> >> > mtk_pcie_port *port)
> >> >  		return err;
> >> >  	}
> >> >
> >> > +	/* Enable MSI */
> >> > +	val = readl_relaxed(port->base + PCIE_MSI_SET_ENABLE_REG);
> >> > +	val |= PCIE_MSI_SET_ENABLE;
> >> > +	writel_relaxed(val, port->base + PCIE_MSI_SET_ENABLE_REG);
> >> > +
> >> > +	val = readl_relaxed(port->base + PCIE_INT_ENABLE_REG);
> >> > +	val |= PCIE_MSI_ENABLE;
> >> > +	writel_relaxed(val, port->base + PCIE_INT_ENABLE_REG);
> >> > +
> >> >  	/* Set PCIe translation windows */
> >> >  	resource_list_for_each_entry(entry, &host->windows) {
> >> >  		struct resource *res = entry->res;
> >> > @@ -286,6 +335,129 @@ static int mtk_pcie_set_affinity(struct irq_data
> >> > *data,
> >> >  	return -EINVAL;
> >> >  }
> >> >
> >> > +static struct irq_chip mtk_msi_irq_chip = {
> >> > +	.name = "MSI",
> >> > +	.irq_ack = irq_chip_ack_parent,
> >> > +};
> >> > +
> >> > +static struct msi_domain_info mtk_msi_domain_info = {
> >> > +	.flags		= (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_PCI_MSIX |
> >> > +			   MSI_FLAG_USE_DEF_CHIP_OPS | MSI_FLAG_MULTI_PCI_MSI),
> >> > +	.chip		= &mtk_msi_irq_chip,
> >> > +};
> >> > +
> >> > +static void mtk_compose_msi_msg(struct irq_data *data, struct msi_msg
> >> > *msg)
> >> > +{
> >> > +	struct mtk_msi_set *msi_set = irq_data_get_irq_chip_data(data);
> >> > +	unsigned long hwirq;
> >> > +
> >> > +	hwirq =	data->hwirq % PCIE_MSI_IRQS_PER_SET;
> >> > +
> >> > +	msg->address_hi = upper_32_bits(msi_set->msg_addr);
> >> > +	msg->address_lo = lower_32_bits(msi_set->msg_addr);
> >> > +	msg->data = hwirq;
> >> > +	dev_dbg(msi_set->dev, "msi#%#lx address_hi %#x address_lo %#x data
> >> > %d\n",
> >> > +		hwirq, msg->address_hi, msg->address_lo, msg->data);
> >> > +}
> >> > +
> >> > +static void mtk_msi_bottom_irq_ack(struct irq_data *data)
> >> > +{
> >> > +	struct mtk_msi_set *msi_set = irq_data_get_irq_chip_data(data);
> >> > +	unsigned long hwirq;
> >> > +
> >> > +	hwirq =	data->hwirq % PCIE_MSI_IRQS_PER_SET;
> >> > +
> >> > +	writel_relaxed(BIT(hwirq), msi_set->base +
> >> > PCIE_MSI_SET_STATUS_OFFSET);
> >> > +}
> >> > +
> >> > +static struct irq_chip mtk_msi_bottom_irq_chip = {
> >> > +	.irq_ack		= mtk_msi_bottom_irq_ack,
> >> > +	.irq_compose_msi_msg	= mtk_compose_msi_msg,
> >> > +	.irq_set_affinity	= mtk_pcie_set_affinity,
> >> > +	.name			= "PCIe",
> >> 
> >> nit: "MSI", rather than "PCIe".
> >> 
> >> > +};
> >> > +
> >> > +static int mtk_msi_bottom_domain_alloc(struct irq_domain *domain,
> >> > +				       unsigned int virq, unsigned int nr_irqs,
> >> > +				       void *arg)
> >> > +{
> >> > +	struct mtk_pcie_port *port = domain->host_data;
> >> > +	struct mtk_msi_set *msi_set;
> >> > +	int i, hwirq, set_idx;
> >> > +
> >> > +	mutex_lock(&port->lock);
> >> > +
> >> > +	hwirq = bitmap_find_free_region(port->msi_irq_in_use,
> >> > PCIE_MSI_IRQS_NUM,
> >> > +					order_base_2(nr_irqs));
> >> > +
> >> > +	mutex_unlock(&port->lock);
> >> > +
> >> > +	if (hwirq < 0)
> >> > +		return -ENOSPC;
> >> > +
> >> > +	set_idx = hwirq / PCIE_MSI_IRQS_PER_SET;
> >> > +	msi_set = &port->msi_sets[set_idx];
> >> > +
> >> > +	for (i = 0; i < nr_irqs; i++)
> >> > +		irq_domain_set_info(domain, virq + i, hwirq + i,
> >> > +				    &mtk_msi_bottom_irq_chip, msi_set,
> >> > +				    handle_edge_irq, NULL, NULL);
> >> > +
> >> > +	return 0;
> >> > +}
> >> > +
> >> > +static void mtk_msi_bottom_domain_free(struct irq_domain *domain,
> >> > +				       unsigned int virq, unsigned int nr_irqs)
> >> > +{
> >> > +	struct mtk_pcie_port *port = domain->host_data;
> >> > +	struct irq_data *data = irq_domain_get_irq_data(domain, virq);
> >> > +
> >> > +	mutex_lock(&port->lock);
> >> > +
> >> > +	bitmap_clear(port->msi_irq_in_use, data->hwirq, nr_irqs);
> >> > +
> >> > +	mutex_unlock(&port->lock);
> >> > +
> >> > +	irq_domain_free_irqs_common(domain, virq, nr_irqs);
> >> > +}
> >> > +
> >> > +static int mtk_msi_bottom_domain_activate(struct irq_domain *domain,
> >> > +					  struct irq_data *data, bool reserve)
> >> > +{
> >> > +	struct mtk_msi_set *msi_set = irq_data_get_irq_chip_data(data);
> >> > +	unsigned long hwirq;
> >> > +	u32 val;
> >> > +
> >> > +	hwirq =	data->hwirq % PCIE_MSI_IRQS_PER_SET;
> >> > +
> >> > +	val = readl_relaxed(msi_set->base + PCIE_MSI_SET_ENABLE_OFFSET);
> >> > +	val |= BIT(hwirq);
> >> > +	writel_relaxed(val, msi_set->base + PCIE_MSI_SET_ENABLE_OFFSET);
> >> 
> >> This isn't an activate. This is an unmask, which suffers from the same
> >> issue as its INTx sibling.
> >> 
> >> > +
> >> > +	return 0;
> >> > +}
> >> > +
> >> > +static void mtk_msi_bottom_domain_deactivate(struct irq_domain
> >> > *domain,
> >> > +					     struct irq_data *data)
> >> > +{
> >> > +	struct mtk_msi_set *msi_set = irq_data_get_irq_chip_data(data);
> >> > +	unsigned long hwirq;
> >> > +	u32 val;
> >> > +
> >> > +	hwirq =	data->hwirq % PCIE_MSI_IRQS_PER_SET;
> >> > +
> >> > +	val = readl_relaxed(msi_set->base + PCIE_MSI_SET_ENABLE_OFFSET);
> >> > +	val &= ~BIT(hwirq);
> >> > +	writel_relaxed(val, msi_set->base + PCIE_MSI_SET_ENABLE_OFFSET);
> >> > +}
> >> 
> >> Same thing, this is a mask. I don't think this block requires any
> >> activate/deactivate callbacks for its lower irqdomain.
> >> 
> >> As it stands, you can't mask a MSI at the low-level, which is
> >> pretty bad (you need to mask them at the PCI source, which can
> >> end-up disabling all vectors in the case of Multi-MSI).
> > 
> > Hi Marc,
> > 
> > Thanks for your review.
> > 
> > This mtk_msi_bottom_domain is the parent domain of pci_msi_domain, but
> > the mask/unmask callback of pci_msi_domain does not call the callback 
> > of
> > its parent. Therefore if these functions are put in the mask/unmask
> > callbacks, they will not have a chance to be called.
> 
> It is for you to wire the callbacks in the mtk_msi_irq_chip irqchip
> so that the request can be forwarded to the parent, without relying
> on the default callbacks:
> 
> static void mtk_mask_msi_irq(struct irq_data *d)
> {
> 	pci_msi_mask_irq(d);
> 	irq_chip_mask_parent(d);
> }
> 
> static void mtk_unmask_msi_irq(struct irq_data *d)
> {
> 	pci_msi_unmask_irq(d);
> 	irq_chip_unmask_parent(d);
> }
> 
> static struct irq_chip mtk_msi_irq_chip = {
>         .name = "MSI",
>         .irq_mask = mtk_mask_msi_irq,
>         .irq_unmask = mtk_unmask_msi_irq,
>         .irq_ack = irq_chip_ack_parent,
> };
> 
> and turn your activate/deactivate into unmask/mask.

I will fix it in the next version ,thanks a lot.
> 
> >> 
> >> > +
> >> > +static const struct irq_domain_ops mtk_msi_bottom_domain_ops = {
> >> > +	.alloc = mtk_msi_bottom_domain_alloc,
> >> > +	.free = mtk_msi_bottom_domain_free,
> >> > +	.activate = mtk_msi_bottom_domain_activate,
> >> > +	.deactivate = mtk_msi_bottom_domain_deactivate,
> >> > +};
> >> > +
> >> >  static void mtk_intx_mask(struct irq_data *data)
> >> >  {
> >> >  	struct mtk_pcie_port *port = irq_data_get_irq_chip_data(data);
> >> > @@ -350,6 +522,9 @@ static int mtk_pcie_init_irq_domains(struct
> >> > mtk_pcie_port *port,
> >> >  {
> >> >  	struct device *dev = port->dev;
> >> >  	struct device_node *intc_node;
> >> > +	struct fwnode_handle *fwnode = of_node_to_fwnode(node);
> >> > +	struct msi_domain_info *info;
> >> > +	int i, ret;
> >> >
> >> >  	/* Setup INTx */
> >> >  	intc_node = of_get_child_by_name(node, "interrupt-controller");
> >> > @@ -365,7 +540,57 @@ static int mtk_pcie_init_irq_domains(struct
> >> > mtk_pcie_port *port,
> >> >  		return -ENODEV;
> >> >  	}
> >> >
> >> > +	/* Setup MSI */
> >> > +	mutex_init(&port->lock);
> >> > +
> >> > +	port->msi_bottom_domain = irq_domain_add_linear(node,
> >> > PCIE_MSI_IRQS_NUM,
> >> > +				  &mtk_msi_bottom_domain_ops, port);
> >> > +	if (!port->msi_bottom_domain) {
> >> > +		dev_info(dev, "failed to create MSI bottom domain\n");
> >> > +		ret = -ENODEV;
> >> > +		goto err_msi_bottom_domain;
> >> > +	}
> >> > +
> >> > +	info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
> >> > +	if (!info) {
> >> > +		ret = -ENOMEM;
> >> > +		goto err_msi_bottom_domain;
> >> > +	}
> >> > +
> >> > +	memcpy(info, &mtk_msi_domain_info, sizeof(*info));
> >> 
> >> Why the memcpy()? There is nothing in mtk_msi_domain_info that is
> >> per-domain, and you should be able to use this structure for all
> >> ports, shouldn't you?
> > 
> > Yes, it used to update the info->chip_data for each port, but since the
> > msi_set has been used for msi_bottom_domain ,it has no effect anymore, 
> > I
> > will drop it in the next version, thanks.
> >> 
> >> > +	info->chip_data = port;
> >> > +
> >> > +	port->msi_domain = pci_msi_create_irq_domain(fwnode, info,
> >> > +						     port->msi_bottom_domain);
> >> > +	if (!port->msi_domain) {
> >> > +		dev_info(dev, "failed to create MSI domain\n");
> >> > +		ret = -ENODEV;
> >> > +		goto err_msi_domain;
> >> > +	}
> >> > +
> >> > +	for (i = 0; i < PCIE_MSI_SET_NUM; i++) {
> >> > +		struct mtk_msi_set *msi_set = &port->msi_sets[i];
> >> > +
> >> > +		msi_set->dev = port->dev;
> >> 
> >> Given that this is only used in a debug message, and that the 
> >> addresses
> >> are already non-ambiguous, you can probably remove this field.
> > 
> > OK, I will remove it in the next version.
> > 
> >> 
> >> > +		msi_set->base = port->base + PCIE_MSI_SET_BASE_REG +
> >> > +				i * PCIE_MSI_SET_OFFSET;
> >> > +		msi_set->msg_addr = port->reg_base + PCIE_MSI_SET_BASE_REG +
> >> > +				    i * PCIE_MSI_SET_OFFSET;
> >> > +
> >> > +		writel_relaxed(lower_32_bits(msi_set->msg_addr), msi_set->base);
> >> > +		writel_relaxed(upper_32_bits(msi_set->msg_addr),
> >> > +			       port->base + PCIE_MSI_SET_ADDR_HI_BASE +
> >> > +			       i * PCIE_MSI_SET_ADDR_HI_OFFSET);
> >> 
> >> Please a comment on what this is doing...
> > 
> > This codes are used to configure the capture address of each MSI set,
> > the lower 32 bits of MSI address should be written to msi_set->base, 
> > but
> > the strange thing is that the address where need to write the higher 32
> > bits are not near each set, they are start from
> > PCIE_MSI_SET_ADDR_HI_BASE, and have PCIE_MSI_SET_ADDR_HI_OFFSET apart.
> > 
> > That's why it looks so weired...
> 
> OK. Just add a comment saying that this programs the MSI capture 
> address.
> 
> Thanks,
> 
>          M.



More information about the linux-arm-kernel mailing list