[PATCH V2 1/7] PCI: dwc: Move MSI functionality related code to separate file

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This change moves dwc PCIe controller specific MSI functionality
into separate file in preparation to allow MSI functionality with
PCIe ECAM driver. Update existing drivers to accommodate this change.

Signed-off-by: Mayank Rana <quic_mrana@xxxxxxxxxxx>
---
 drivers/pci/controller/dwc/Makefile               |   2 +-
 drivers/pci/controller/dwc/pci-keystone.c         |  12 +-
 drivers/pci/controller/dwc/pcie-designware-host.c | 420 +---------------------
 drivers/pci/controller/dwc/pcie-designware-msi.c  | 409 +++++++++++++++++++++
 drivers/pci/controller/dwc/pcie-designware-msi.h  |  43 +++
 drivers/pci/controller/dwc/pcie-designware.c      |   1 +
 drivers/pci/controller/dwc/pcie-designware.h      |  26 +-
 drivers/pci/controller/dwc/pcie-rcar-gen4.c       |   1 +
 drivers/pci/controller/dwc/pcie-tegra194.c        |   5 +-
 9 files changed, 484 insertions(+), 435 deletions(-)
 create mode 100644 drivers/pci/controller/dwc/pcie-designware-msi.c
 create mode 100644 drivers/pci/controller/dwc/pcie-designware-msi.h

diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile
index bac103f..2ecc603 100644
--- a/drivers/pci/controller/dwc/Makefile
+++ b/drivers/pci/controller/dwc/Makefile
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_PCIE_DW) += pcie-designware.o
-obj-$(CONFIG_PCIE_DW_HOST) += pcie-designware-host.o
+obj-$(CONFIG_PCIE_DW_HOST) += pcie-designware-host.o pcie-designware-msi.o
 obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o
 obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o
 obj-$(CONFIG_PCIE_BT1) += pcie-bt1.o
diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c
index cd0e002..b95d319 100644
--- a/drivers/pci/controller/dwc/pci-keystone.c
+++ b/drivers/pci/controller/dwc/pci-keystone.c
@@ -307,8 +307,14 @@ static int ks_pcie_msi_host_init(struct dw_pcie_rp *pp)
 	 */
 	dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, ks_pcie->app.start);
 
-	pp->msi_irq_chip = &ks_pcie_msi_irq_chip;
-	return dw_pcie_allocate_domains(pp);
+	pp->msi = devm_kzalloc(pci->dev, sizeof(struct dw_msi *), GFP_KERNEL);
+	if (pp->msi == NULL)
+		return -ENOMEM;
+
+	pp->msi->msi_irq_chip = &ks_pcie_msi_irq_chip;
+	pp->msi->dev =  pci->dev;
+	pp->msi->private_data = pp;
+	return dw_pcie_allocate_domains(pp->msi);
 }
 
 static void ks_pcie_handle_intx_irq(struct keystone_pcie *ks_pcie,
@@ -322,7 +328,7 @@ static void ks_pcie_handle_intx_irq(struct keystone_pcie *ks_pcie,
 
 	if (BIT(0) & pending) {
 		dev_dbg(dev, ": irq: irq_offset %d", offset);
-		generic_handle_domain_irq(ks_pcie->intx_irq_domain, offset);
+		generic_handle_domain_irq(ks_pcie->msi->intx_irq_domain, offset);
 	}
 
 	/* EOI the INTx interrupt */
diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c
index a0822d5..3dcf88a 100644
--- a/drivers/pci/controller/dwc/pcie-designware-host.c
+++ b/drivers/pci/controller/dwc/pcie-designware-host.c
@@ -9,9 +9,6 @@
  */
 
 #include <linux/iopoll.h>
-#include <linux/irqchip/chained_irq.h>
-#include <linux/irqdomain.h>
-#include <linux/msi.h>
 #include <linux/of_address.h>
 #include <linux/of_pci.h>
 #include <linux/pci_regs.h>
@@ -19,385 +16,11 @@
 
 #include "../../pci.h"
 #include "pcie-designware.h"
+#include "pcie-designware-msi.h"
 
 static struct pci_ops dw_pcie_ops;
 static struct pci_ops dw_child_pcie_ops;
 
-static void dw_msi_ack_irq(struct irq_data *d)
-{
-	irq_chip_ack_parent(d);
-}
-
-static void dw_msi_mask_irq(struct irq_data *d)
-{
-	pci_msi_mask_irq(d);
-	irq_chip_mask_parent(d);
-}
-
-static void dw_msi_unmask_irq(struct irq_data *d)
-{
-	pci_msi_unmask_irq(d);
-	irq_chip_unmask_parent(d);
-}
-
-static struct irq_chip dw_pcie_msi_irq_chip = {
-	.name = "PCI-MSI",
-	.irq_ack = dw_msi_ack_irq,
-	.irq_mask = dw_msi_mask_irq,
-	.irq_unmask = dw_msi_unmask_irq,
-};
-
-static struct msi_domain_info dw_pcie_msi_domain_info = {
-	.flags	= (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
-		   MSI_FLAG_PCI_MSIX | MSI_FLAG_MULTI_PCI_MSI),
-	.chip	= &dw_pcie_msi_irq_chip,
-};
-
-/* MSI int handler */
-irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp)
-{
-	int i, pos;
-	unsigned long val;
-	u32 status, num_ctrls;
-	irqreturn_t ret = IRQ_NONE;
-	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
-
-	num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
-
-	for (i = 0; i < num_ctrls; i++) {
-		status = dw_pcie_readl_dbi(pci, PCIE_MSI_INTR0_STATUS +
-					   (i * MSI_REG_CTRL_BLOCK_SIZE));
-		if (!status)
-			continue;
-
-		ret = IRQ_HANDLED;
-		val = status;
-		pos = 0;
-		while ((pos = find_next_bit(&val, MAX_MSI_IRQS_PER_CTRL,
-					    pos)) != MAX_MSI_IRQS_PER_CTRL) {
-			generic_handle_domain_irq(pp->irq_domain,
-						  (i * MAX_MSI_IRQS_PER_CTRL) +
-						  pos);
-			pos++;
-		}
-	}
-
-	return ret;
-}
-
-/* Chained MSI interrupt service routine */
-static void dw_chained_msi_isr(struct irq_desc *desc)
-{
-	struct irq_chip *chip = irq_desc_get_chip(desc);
-	struct dw_pcie_rp *pp;
-
-	chained_irq_enter(chip, desc);
-
-	pp = irq_desc_get_handler_data(desc);
-	dw_handle_msi_irq(pp);
-
-	chained_irq_exit(chip, desc);
-}
-
-static void dw_pci_setup_msi_msg(struct irq_data *d, struct msi_msg *msg)
-{
-	struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
-	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
-	u64 msi_target;
-
-	msi_target = (u64)pp->msi_data;
-
-	msg->address_lo = lower_32_bits(msi_target);
-	msg->address_hi = upper_32_bits(msi_target);
-
-	msg->data = d->hwirq;
-
-	dev_dbg(pci->dev, "msi#%d address_hi %#x address_lo %#x\n",
-		(int)d->hwirq, msg->address_hi, msg->address_lo);
-}
-
-static int dw_pci_msi_set_affinity(struct irq_data *d,
-				   const struct cpumask *mask, bool force)
-{
-	return -EINVAL;
-}
-
-static void dw_pci_bottom_mask(struct irq_data *d)
-{
-	struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
-	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
-	unsigned int res, bit, ctrl;
-	unsigned long flags;
-
-	raw_spin_lock_irqsave(&pp->lock, flags);
-
-	ctrl = d->hwirq / MAX_MSI_IRQS_PER_CTRL;
-	res = ctrl * MSI_REG_CTRL_BLOCK_SIZE;
-	bit = d->hwirq % MAX_MSI_IRQS_PER_CTRL;
-
-	pp->irq_mask[ctrl] |= BIT(bit);
-	dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + res, pp->irq_mask[ctrl]);
-
-	raw_spin_unlock_irqrestore(&pp->lock, flags);
-}
-
-static void dw_pci_bottom_unmask(struct irq_data *d)
-{
-	struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
-	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
-	unsigned int res, bit, ctrl;
-	unsigned long flags;
-
-	raw_spin_lock_irqsave(&pp->lock, flags);
-
-	ctrl = d->hwirq / MAX_MSI_IRQS_PER_CTRL;
-	res = ctrl * MSI_REG_CTRL_BLOCK_SIZE;
-	bit = d->hwirq % MAX_MSI_IRQS_PER_CTRL;
-
-	pp->irq_mask[ctrl] &= ~BIT(bit);
-	dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + res, pp->irq_mask[ctrl]);
-
-	raw_spin_unlock_irqrestore(&pp->lock, flags);
-}
-
-static void dw_pci_bottom_ack(struct irq_data *d)
-{
-	struct dw_pcie_rp *pp  = irq_data_get_irq_chip_data(d);
-	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
-	unsigned int res, bit, ctrl;
-
-	ctrl = d->hwirq / MAX_MSI_IRQS_PER_CTRL;
-	res = ctrl * MSI_REG_CTRL_BLOCK_SIZE;
-	bit = d->hwirq % MAX_MSI_IRQS_PER_CTRL;
-
-	dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_STATUS + res, BIT(bit));
-}
-
-static struct irq_chip dw_pci_msi_bottom_irq_chip = {
-	.name = "DWPCI-MSI",
-	.irq_ack = dw_pci_bottom_ack,
-	.irq_compose_msi_msg = dw_pci_setup_msi_msg,
-	.irq_set_affinity = dw_pci_msi_set_affinity,
-	.irq_mask = dw_pci_bottom_mask,
-	.irq_unmask = dw_pci_bottom_unmask,
-};
-
-static int dw_pcie_irq_domain_alloc(struct irq_domain *domain,
-				    unsigned int virq, unsigned int nr_irqs,
-				    void *args)
-{
-	struct dw_pcie_rp *pp = domain->host_data;
-	unsigned long flags;
-	u32 i;
-	int bit;
-
-	raw_spin_lock_irqsave(&pp->lock, flags);
-
-	bit = bitmap_find_free_region(pp->msi_irq_in_use, pp->num_vectors,
-				      order_base_2(nr_irqs));
-
-	raw_spin_unlock_irqrestore(&pp->lock, flags);
-
-	if (bit < 0)
-		return -ENOSPC;
-
-	for (i = 0; i < nr_irqs; i++)
-		irq_domain_set_info(domain, virq + i, bit + i,
-				    pp->msi_irq_chip,
-				    pp, handle_edge_irq,
-				    NULL, NULL);
-
-	return 0;
-}
-
-static void dw_pcie_irq_domain_free(struct irq_domain *domain,
-				    unsigned int virq, unsigned int nr_irqs)
-{
-	struct irq_data *d = irq_domain_get_irq_data(domain, virq);
-	struct dw_pcie_rp *pp = domain->host_data;
-	unsigned long flags;
-
-	raw_spin_lock_irqsave(&pp->lock, flags);
-
-	bitmap_release_region(pp->msi_irq_in_use, d->hwirq,
-			      order_base_2(nr_irqs));
-
-	raw_spin_unlock_irqrestore(&pp->lock, flags);
-}
-
-static const struct irq_domain_ops dw_pcie_msi_domain_ops = {
-	.alloc	= dw_pcie_irq_domain_alloc,
-	.free	= dw_pcie_irq_domain_free,
-};
-
-int dw_pcie_allocate_domains(struct dw_pcie_rp *pp)
-{
-	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
-	struct fwnode_handle *fwnode = of_node_to_fwnode(pci->dev->of_node);
-
-	pp->irq_domain = irq_domain_create_linear(fwnode, pp->num_vectors,
-					       &dw_pcie_msi_domain_ops, pp);
-	if (!pp->irq_domain) {
-		dev_err(pci->dev, "Failed to create IRQ domain\n");
-		return -ENOMEM;
-	}
-
-	irq_domain_update_bus_token(pp->irq_domain, DOMAIN_BUS_NEXUS);
-
-	pp->msi_domain = pci_msi_create_irq_domain(fwnode,
-						   &dw_pcie_msi_domain_info,
-						   pp->irq_domain);
-	if (!pp->msi_domain) {
-		dev_err(pci->dev, "Failed to create MSI domain\n");
-		irq_domain_remove(pp->irq_domain);
-		return -ENOMEM;
-	}
-
-	return 0;
-}
-
-static void dw_pcie_free_msi(struct dw_pcie_rp *pp)
-{
-	u32 ctrl;
-
-	for (ctrl = 0; ctrl < MAX_MSI_CTRLS; ctrl++) {
-		if (pp->msi_irq[ctrl] > 0)
-			irq_set_chained_handler_and_data(pp->msi_irq[ctrl],
-							 NULL, NULL);
-	}
-
-	irq_domain_remove(pp->msi_domain);
-	irq_domain_remove(pp->irq_domain);
-}
-
-static void dw_pcie_msi_init(struct dw_pcie_rp *pp)
-{
-	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
-	u64 msi_target = (u64)pp->msi_data;
-
-	if (!pci_msi_enabled() || !pp->has_msi_ctrl)
-		return;
-
-	/* Program the msi_data */
-	dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_LO, lower_32_bits(msi_target));
-	dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_HI, upper_32_bits(msi_target));
-}
-
-static int dw_pcie_parse_split_msi_irq(struct dw_pcie_rp *pp)
-{
-	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
-	struct device *dev = pci->dev;
-	struct platform_device *pdev = to_platform_device(dev);
-	u32 ctrl, max_vectors;
-	int irq;
-
-	/* Parse any "msiX" IRQs described in the devicetree */
-	for (ctrl = 0; ctrl < MAX_MSI_CTRLS; ctrl++) {
-		char msi_name[] = "msiX";
-
-		msi_name[3] = '0' + ctrl;
-		irq = platform_get_irq_byname_optional(pdev, msi_name);
-		if (irq == -ENXIO)
-			break;
-		if (irq < 0)
-			return dev_err_probe(dev, irq,
-					     "Failed to parse MSI IRQ '%s'\n",
-					     msi_name);
-
-		pp->msi_irq[ctrl] = irq;
-	}
-
-	/* If no "msiX" IRQs, caller should fallback to "msi" IRQ */
-	if (ctrl == 0)
-		return -ENXIO;
-
-	max_vectors = ctrl * MAX_MSI_IRQS_PER_CTRL;
-	if (pp->num_vectors > max_vectors) {
-		dev_warn(dev, "Exceeding number of MSI vectors, limiting to %u\n",
-			 max_vectors);
-		pp->num_vectors = max_vectors;
-	}
-	if (!pp->num_vectors)
-		pp->num_vectors = max_vectors;
-
-	return 0;
-}
-
-static int dw_pcie_msi_host_init(struct dw_pcie_rp *pp)
-{
-	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
-	struct device *dev = pci->dev;
-	struct platform_device *pdev = to_platform_device(dev);
-	u64 *msi_vaddr = NULL;
-	int ret;
-	u32 ctrl, num_ctrls;
-
-	for (ctrl = 0; ctrl < MAX_MSI_CTRLS; ctrl++)
-		pp->irq_mask[ctrl] = ~0;
-
-	if (!pp->msi_irq[0]) {
-		ret = dw_pcie_parse_split_msi_irq(pp);
-		if (ret < 0 && ret != -ENXIO)
-			return ret;
-	}
-
-	if (!pp->num_vectors)
-		pp->num_vectors = MSI_DEF_NUM_VECTORS;
-	num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
-
-	if (!pp->msi_irq[0]) {
-		pp->msi_irq[0] = platform_get_irq_byname_optional(pdev, "msi");
-		if (pp->msi_irq[0] < 0) {
-			pp->msi_irq[0] = platform_get_irq(pdev, 0);
-			if (pp->msi_irq[0] < 0)
-				return pp->msi_irq[0];
-		}
-	}
-
-	dev_dbg(dev, "Using %d MSI vectors\n", pp->num_vectors);
-
-	pp->msi_irq_chip = &dw_pci_msi_bottom_irq_chip;
-
-	ret = dw_pcie_allocate_domains(pp);
-	if (ret)
-		return ret;
-
-	for (ctrl = 0; ctrl < num_ctrls; ctrl++) {
-		if (pp->msi_irq[ctrl] > 0)
-			irq_set_chained_handler_and_data(pp->msi_irq[ctrl],
-						    dw_chained_msi_isr, pp);
-	}
-
-	/*
-	 * Even though the iMSI-RX Module supports 64-bit addresses some
-	 * peripheral PCIe devices may lack 64-bit message support. In
-	 * order not to miss MSI TLPs from those devices the MSI target
-	 * address has to be within the lowest 4GB.
-	 *
-	 * Note until there is a better alternative found the reservation is
-	 * done by allocating from the artificially limited DMA-coherent
-	 * memory.
-	 */
-	ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
-	if (!ret)
-		msi_vaddr = dmam_alloc_coherent(dev, sizeof(u64), &pp->msi_data,
-						GFP_KERNEL);
-
-	if (!msi_vaddr) {
-		dev_warn(dev, "Failed to allocate 32-bit MSI address\n");
-		dma_set_coherent_mask(dev, DMA_BIT_MASK(64));
-		msi_vaddr = dmam_alloc_coherent(dev, sizeof(u64), &pp->msi_data,
-						GFP_KERNEL);
-		if (!msi_vaddr) {
-			dev_err(dev, "Failed to allocate MSI address\n");
-			dw_pcie_free_msi(pp);
-			return -ENOMEM;
-		}
-	}
-
-	return 0;
-}
-
 static void dw_pcie_host_request_msg_tlp_res(struct dw_pcie_rp *pp)
 {
 	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
@@ -433,6 +56,7 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp)
 	struct resource_entry *win;
 	struct pci_host_bridge *bridge;
 	struct resource *res;
+	bool has_msi_ctrl;
 	int ret;
 
 	raw_spin_lock_init(&pp->lock);
@@ -479,15 +103,15 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp)
 	}
 
 	if (pci_msi_enabled()) {
-		pp->has_msi_ctrl = !(pp->ops->msi_init ||
-				     of_property_read_bool(np, "msi-parent") ||
-				     of_property_read_bool(np, "msi-map"));
+		has_msi_ctrl = !(pp->ops->msi_init ||
+					of_property_read_bool(np, "msi-parent") ||
+					of_property_read_bool(np, "msi-map"));
 
 		/*
 		 * For the has_msi_ctrl case the default assignment is handled
 		 * in the dw_pcie_msi_host_init().
 		 */
-		if (!pp->has_msi_ctrl && !pp->num_vectors) {
+		if (!has_msi_ctrl && !pp->num_vectors) {
 			pp->num_vectors = MSI_DEF_NUM_VECTORS;
 		} else if (pp->num_vectors > MAX_MSI_IRQS) {
 			dev_err(dev, "Invalid number of vectors\n");
@@ -499,10 +123,12 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp)
 			ret = pp->ops->msi_init(pp);
 			if (ret < 0)
 				goto err_deinit_host;
-		} else if (pp->has_msi_ctrl) {
-			ret = dw_pcie_msi_host_init(pp);
-			if (ret < 0)
+		} else if (has_msi_ctrl) {
+			pp->msi = dw_pcie_msi_host_init(pdev, pp, pp->num_vectors);
+			if (IS_ERR(pp->msi)) {
+				ret = PTR_ERR(pp->msi);
 				goto err_deinit_host;
+			}
 		}
 	}
 
@@ -557,8 +183,7 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp)
 	dw_pcie_edma_remove(pci);
 
 err_free_msi:
-	if (pp->has_msi_ctrl)
-		dw_pcie_free_msi(pp);
+	dw_pcie_free_msi(pp->msi);
 
 err_deinit_host:
 	if (pp->ops->deinit)
@@ -579,8 +204,7 @@ void dw_pcie_host_deinit(struct dw_pcie_rp *pp)
 
 	dw_pcie_edma_remove(pci);
 
-	if (pp->has_msi_ctrl)
-		dw_pcie_free_msi(pp);
+	dw_pcie_free_msi(pp->msi);
 
 	if (pp->ops->deinit)
 		pp->ops->deinit(pp);
@@ -808,7 +432,7 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp)
 int dw_pcie_setup_rc(struct dw_pcie_rp *pp)
 {
 	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
-	u32 val, ctrl, num_ctrls;
+	u32 val;
 	int ret;
 
 	/*
@@ -819,21 +443,7 @@ int dw_pcie_setup_rc(struct dw_pcie_rp *pp)
 
 	dw_pcie_setup(pci);
 
-	if (pp->has_msi_ctrl) {
-		num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
-
-		/* Initialize IRQ Status array */
-		for (ctrl = 0; ctrl < num_ctrls; ctrl++) {
-			dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK +
-					    (ctrl * MSI_REG_CTRL_BLOCK_SIZE),
-					    pp->irq_mask[ctrl]);
-			dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_ENABLE +
-					    (ctrl * MSI_REG_CTRL_BLOCK_SIZE),
-					    ~0);
-		}
-	}
-
-	dw_pcie_msi_init(pp);
+	dw_pcie_msi_init(pp->msi);
 
 	/* Setup RC BARs */
 	dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0x00000004);
diff --git a/drivers/pci/controller/dwc/pcie-designware-msi.c b/drivers/pci/controller/dwc/pcie-designware-msi.c
new file mode 100644
index 0000000..39fe5be
--- /dev/null
+++ b/drivers/pci/controller/dwc/pcie-designware-msi.c
@@ -0,0 +1,409 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *		https://www.samsung.com
+ *
+ * Author: Jingoo Han <jg1.han@xxxxxxxxxxx>
+ */
+
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/msi.h>
+#include <linux/platform_device.h>
+
+#include "pcie-designware.h"
+#include "pcie-designware-msi.h"
+
+static void dw_msi_ack_irq(struct irq_data *d)
+{
+	irq_chip_ack_parent(d);
+}
+
+static void dw_msi_mask_irq(struct irq_data *d)
+{
+	pci_msi_mask_irq(d);
+	irq_chip_mask_parent(d);
+}
+
+static void dw_msi_unmask_irq(struct irq_data *d)
+{
+	pci_msi_unmask_irq(d);
+	irq_chip_unmask_parent(d);
+}
+
+static struct irq_chip dw_pcie_msi_irq_chip = {
+	.name = "PCI-MSI",
+	.irq_ack = dw_msi_ack_irq,
+	.irq_mask = dw_msi_mask_irq,
+	.irq_unmask = dw_msi_unmask_irq,
+};
+
+static struct msi_domain_info dw_pcie_msi_domain_info = {
+	.flags	= (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
+		   MSI_FLAG_PCI_MSIX | MSI_FLAG_MULTI_PCI_MSI),
+	.chip	= &dw_pcie_msi_irq_chip,
+};
+
+/* MSI int handler */
+irqreturn_t dw_handle_msi_irq(struct dw_msi *msi)
+{
+	int i, pos;
+	unsigned long val;
+	u32 status, num_ctrls;
+	irqreturn_t ret = IRQ_NONE;
+	struct dw_pcie *pci = to_dw_pcie_from_pp(msi->pp);
+
+	num_ctrls = msi->num_vectors / MAX_MSI_IRQS_PER_CTRL;
+
+	for (i = 0; i < num_ctrls; i++) {
+		status = dw_pcie_readl_dbi(pci, PCIE_MSI_INTR0_STATUS +
+					   (i * MSI_REG_CTRL_BLOCK_SIZE));
+		if (!status)
+			continue;
+
+		ret = IRQ_HANDLED;
+		val = status;
+		pos = 0;
+		while ((pos = find_next_bit(&val, MAX_MSI_IRQS_PER_CTRL,
+					    pos)) != MAX_MSI_IRQS_PER_CTRL) {
+			generic_handle_domain_irq(msi->irq_domain,
+						  (i * MAX_MSI_IRQS_PER_CTRL) +
+						  pos);
+			pos++;
+		}
+	}
+
+	return ret;
+}
+
+/* Chained MSI interrupt service routine */
+static void dw_chained_msi_isr(struct irq_desc *desc)
+{
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	struct dw_msi *msi;
+
+	chained_irq_enter(chip, desc);
+
+	msi = irq_desc_get_handler_data(desc);
+	dw_handle_msi_irq(msi);
+
+	chained_irq_exit(chip, desc);
+}
+
+static void dw_pci_setup_msi_msg(struct irq_data *d, struct msi_msg *msg)
+{
+	struct dw_msi *msi = irq_data_get_irq_chip_data(d);
+	u64 msi_target;
+
+	msi_target = (u64)msi->msi_data;
+
+	msg->address_lo = lower_32_bits(msi_target);
+	msg->address_hi = upper_32_bits(msi_target);
+
+	msg->data = d->hwirq;
+
+	dev_dbg(msi->dev, "msi#%d address_hi %#x address_lo %#x\n",
+		(int)d->hwirq, msg->address_hi, msg->address_lo);
+}
+
+static int dw_pci_msi_set_affinity(struct irq_data *d,
+				   const struct cpumask *mask, bool force)
+{
+	return -EINVAL;
+}
+
+static void dw_pci_bottom_mask(struct irq_data *d)
+{
+	struct dw_msi *msi = irq_data_get_irq_chip_data(d);
+	struct dw_pcie *pci = to_dw_pcie_from_pp(msi->pp);
+	unsigned int res, bit, ctrl;
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&msi->lock, flags);
+
+	ctrl = d->hwirq / MAX_MSI_IRQS_PER_CTRL;
+	res = ctrl * MSI_REG_CTRL_BLOCK_SIZE;
+	bit = d->hwirq % MAX_MSI_IRQS_PER_CTRL;
+
+	msi->irq_mask[ctrl] |= BIT(bit);
+	dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + res, msi->irq_mask[ctrl]);
+
+	raw_spin_unlock_irqrestore(&msi->lock, flags);
+}
+
+static void dw_pci_bottom_unmask(struct irq_data *d)
+{
+	struct dw_msi *msi = irq_data_get_irq_chip_data(d);
+	struct dw_pcie *pci = to_dw_pcie_from_pp(msi->pp);
+	unsigned int res, bit, ctrl;
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&msi->lock, flags);
+
+	ctrl = d->hwirq / MAX_MSI_IRQS_PER_CTRL;
+	res = ctrl * MSI_REG_CTRL_BLOCK_SIZE;
+	bit = d->hwirq % MAX_MSI_IRQS_PER_CTRL;
+
+	msi->irq_mask[ctrl] &= ~BIT(bit);
+	dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + res, msi->irq_mask[ctrl]);
+
+	raw_spin_unlock_irqrestore(&msi->lock, flags);
+}
+
+static void dw_pci_bottom_ack(struct irq_data *d)
+{
+	struct dw_msi *msi  = irq_data_get_irq_chip_data(d);
+	struct dw_pcie *pci = to_dw_pcie_from_pp(msi->pp);
+	unsigned int res, bit, ctrl;
+
+	ctrl = d->hwirq / MAX_MSI_IRQS_PER_CTRL;
+	res = ctrl * MSI_REG_CTRL_BLOCK_SIZE;
+	bit = d->hwirq % MAX_MSI_IRQS_PER_CTRL;
+
+	dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_STATUS + res, BIT(bit));
+}
+
+static struct irq_chip dw_pci_msi_bottom_irq_chip = {
+	.name = "DWPCI-MSI",
+	.irq_ack = dw_pci_bottom_ack,
+	.irq_compose_msi_msg = dw_pci_setup_msi_msg,
+	.irq_set_affinity = dw_pci_msi_set_affinity,
+	.irq_mask = dw_pci_bottom_mask,
+	.irq_unmask = dw_pci_bottom_unmask,
+};
+
+static int dw_pcie_irq_domain_alloc(struct irq_domain *domain,
+				    unsigned int virq, unsigned int nr_irqs,
+				    void *args)
+{
+	struct dw_msi *msi = domain->host_data;
+	unsigned long flags;
+	u32 i;
+	int bit;
+
+	raw_spin_lock_irqsave(&msi->lock, flags);
+
+	bit = bitmap_find_free_region(msi->msi_irq_in_use, msi->num_vectors,
+				      order_base_2(nr_irqs));
+
+	raw_spin_unlock_irqrestore(&msi->lock, flags);
+
+	if (bit < 0)
+		return -ENOSPC;
+
+	for (i = 0; i < nr_irqs; i++)
+		irq_domain_set_info(domain, virq + i, bit + i,
+				    msi->msi_irq_chip,
+				    msi, handle_edge_irq,
+				    NULL, NULL);
+
+	return 0;
+}
+
+static void dw_pcie_irq_domain_free(struct irq_domain *domain,
+				    unsigned int virq, unsigned int nr_irqs)
+{
+	struct irq_data *d = irq_domain_get_irq_data(domain, virq);
+	struct dw_msi *msi = domain->host_data;
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&msi->lock, flags);
+
+	bitmap_release_region(msi->msi_irq_in_use, d->hwirq,
+			      order_base_2(nr_irqs));
+
+	raw_spin_unlock_irqrestore(&msi->lock, flags);
+}
+
+static const struct irq_domain_ops dw_pcie_msi_domain_ops = {
+	.alloc	= dw_pcie_irq_domain_alloc,
+	.free	= dw_pcie_irq_domain_free,
+};
+
+int dw_pcie_allocate_domains(struct dw_msi *msi)
+{
+	struct fwnode_handle *fwnode = of_node_to_fwnode(msi->dev->of_node);
+
+	msi->irq_domain = irq_domain_create_linear(fwnode, msi->num_vectors,
+				&dw_pcie_msi_domain_ops,
+				msi->private_data ? msi->private_data : msi);
+	if (!msi->irq_domain) {
+		dev_err(msi->dev, "Failed to create IRQ domain\n");
+		return -ENOMEM;
+	}
+
+	irq_domain_update_bus_token(msi->irq_domain, DOMAIN_BUS_NEXUS);
+
+	msi->msi_domain = pci_msi_create_irq_domain(fwnode,
+						   &dw_pcie_msi_domain_info,
+						   msi->irq_domain);
+	if (!msi->msi_domain) {
+		dev_err(msi->dev, "Failed to create MSI domain\n");
+		irq_domain_remove(msi->irq_domain);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+void dw_pcie_free_msi(struct dw_msi *msi)
+{
+	u32 ctrl;
+
+	for (ctrl = 0; ctrl < MAX_MSI_CTRLS; ctrl++) {
+		if (msi->msi_irq[ctrl] > 0)
+			irq_set_chained_handler_and_data(msi->msi_irq[ctrl],
+							 NULL, NULL);
+	}
+
+	irq_domain_remove(msi->msi_domain);
+	irq_domain_remove(msi->irq_domain);
+}
+
+void dw_pcie_msi_init(struct dw_msi *msi)
+{
+	struct dw_pcie *pci = to_dw_pcie_from_pp(msi->pp);
+	u32 ctrl, num_ctrls;
+	u64 msi_target;
+
+	if (!pci_msi_enabled() || !msi->has_msi_ctrl)
+		return;
+
+	msi_target = (u64)msi->msi_data;
+	num_ctrls = msi->num_vectors / MAX_MSI_IRQS_PER_CTRL;
+	/* Initialize IRQ Status array */
+	for (ctrl = 0; ctrl < num_ctrls; ctrl++) {
+		dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK +
+				(ctrl * MSI_REG_CTRL_BLOCK_SIZE),
+				msi->irq_mask[ctrl]);
+		dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_ENABLE +
+				(ctrl * MSI_REG_CTRL_BLOCK_SIZE), ~0);
+	}
+
+	/* Program the msi_data */
+	dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_LO, lower_32_bits(msi_target));
+	dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_HI, upper_32_bits(msi_target));
+}
+
+static int dw_pcie_parse_split_msi_irq(struct dw_msi *msi, struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	u32 ctrl, max_vectors;
+	int irq;
+
+	/* Parse any "msiX" IRQs described in the devicetree */
+	for (ctrl = 0; ctrl < MAX_MSI_CTRLS; ctrl++) {
+		char msi_name[] = "msiX";
+
+		msi_name[3] = '0' + ctrl;
+		irq = platform_get_irq_byname_optional(pdev, msi_name);
+		if (irq == -ENXIO)
+			break;
+		if (irq < 0)
+			return dev_err_probe(dev, irq,
+					     "Failed to parse MSI IRQ '%s'\n",
+					     msi_name);
+
+		msi->msi_irq[ctrl] = irq;
+	}
+
+	/* If no "msiX" IRQs, caller should fallback to "msi" IRQ */
+	if (ctrl == 0)
+		return -ENXIO;
+
+	max_vectors = ctrl * MAX_MSI_IRQS_PER_CTRL;
+	if (msi->num_vectors > max_vectors) {
+		dev_warn(dev, "Exceeding number of MSI vectors, limiting to %u\n",
+			 max_vectors);
+		msi->num_vectors = max_vectors;
+	}
+	if (!msi->num_vectors)
+		msi->num_vectors = max_vectors;
+
+	return 0;
+}
+
+struct dw_msi *dw_pcie_msi_host_init(struct platform_device *pdev,
+				void *pp, u32 num_vectors)
+{
+	struct device *dev = &pdev->dev;
+	u64 *msi_vaddr = NULL;
+	u32 ctrl, num_ctrls;
+	struct dw_msi *msi;
+	int ret;
+
+	msi = devm_kzalloc(dev, sizeof(*msi), GFP_KERNEL);
+	if (msi == NULL)
+		return ERR_PTR(-ENOMEM);
+
+	for (ctrl = 0; ctrl < MAX_MSI_CTRLS; ctrl++)
+		msi->irq_mask[ctrl] = ~0;
+
+	raw_spin_lock_init(&msi->lock);
+	msi->dev = dev;
+	msi->pp = pp;
+	msi->has_msi_ctrl = true;
+	msi->num_vectors = num_vectors;
+
+	if (!msi->msi_irq[0]) {
+		ret = dw_pcie_parse_split_msi_irq(msi, pdev);
+		if (ret < 0 && ret != -ENXIO)
+			return ERR_PTR(ret);
+	}
+
+	if (!msi->num_vectors)
+		msi->num_vectors = MSI_DEF_NUM_VECTORS;
+	num_ctrls = msi->num_vectors / MAX_MSI_IRQS_PER_CTRL;
+
+	if (!msi->msi_irq[0]) {
+		msi->msi_irq[0] = platform_get_irq_byname_optional(pdev, "msi");
+		if (msi->msi_irq[0] < 0) {
+			msi->msi_irq[0] = platform_get_irq(pdev, 0);
+			if (msi->msi_irq[0] < 0)
+				return ERR_PTR(msi->msi_irq[0]);
+		}
+	}
+
+	dev_dbg(dev, "Using %d MSI vectors\n", msi->num_vectors);
+
+	msi->msi_irq_chip = &dw_pci_msi_bottom_irq_chip;
+
+	ret = dw_pcie_allocate_domains(msi);
+	if (ret)
+		return ERR_PTR(ret);
+
+	for (ctrl = 0; ctrl < num_ctrls; ctrl++) {
+		if (msi->msi_irq[ctrl] > 0)
+			irq_set_chained_handler_and_data(msi->msi_irq[ctrl],
+						    dw_chained_msi_isr, msi);
+	}
+
+	/*
+	 * Even though the iMSI-RX Module supports 64-bit addresses some
+	 * peripheral PCIe devices may lack 64-bit message support. In
+	 * order not to miss MSI TLPs from those devices the MSI target
+	 * address has to be within the lowest 4GB.
+	 *
+	 * Note until there is a better alternative found the reservation is
+	 * done by allocating from the artificially limited DMA-coherent
+	 * memory.
+	 */
+	ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
+	if (!ret)
+		msi_vaddr = dmam_alloc_coherent(dev, sizeof(u64), &msi->msi_data,
+						GFP_KERNEL);
+
+	if (!msi_vaddr) {
+		dev_warn(dev, "Failed to allocate 32-bit MSI address\n");
+		dma_set_coherent_mask(dev, DMA_BIT_MASK(64));
+		msi_vaddr = dmam_alloc_coherent(dev, sizeof(u64), &msi->msi_data,
+						GFP_KERNEL);
+		if (!msi_vaddr) {
+			dev_err(dev, "Failed to allocate MSI address\n");
+			dw_pcie_free_msi(msi);
+			return ERR_PTR(-ENOMEM);
+		}
+	}
+
+	return msi;
+}
diff --git a/drivers/pci/controller/dwc/pcie-designware-msi.h b/drivers/pci/controller/dwc/pcie-designware-msi.h
new file mode 100644
index 0000000..633156e
--- /dev/null
+++ b/drivers/pci/controller/dwc/pcie-designware-msi.h
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synopsys DesignWare PCIe host controller driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *		https://www.samsung.com
+ *
+ * Author: Jingoo Han <jg1.han@xxxxxxxxxxx>
+ */
+#ifndef _PCIE_DESIGNWARE_MSI_H
+#define _PCIE_DESIGNWARE_MSI_H
+
+#include "../../pci.h"
+
+#define MAX_MSI_IRQS			256
+#define MAX_MSI_IRQS_PER_CTRL		32
+#define MAX_MSI_CTRLS			(MAX_MSI_IRQS / MAX_MSI_IRQS_PER_CTRL)
+#define MSI_REG_CTRL_BLOCK_SIZE		12
+#define MSI_DEF_NUM_VECTORS		32
+
+struct dw_msi {
+	struct device		*dev;
+	struct irq_domain	*irq_domain;
+	struct irq_domain	*msi_domain;
+	struct irq_chip		*msi_irq_chip;
+	int			msi_irq[MAX_MSI_CTRLS];
+	dma_addr_t		msi_data;
+	u32			num_vectors;
+	u32			irq_mask[MAX_MSI_CTRLS];
+	raw_spinlock_t		lock;
+	DECLARE_BITMAP(msi_irq_in_use, MAX_MSI_IRQS);
+	bool                    has_msi_ctrl;
+	void			*private_data;
+	void			*pp;
+};
+
+struct dw_msi *dw_pcie_msi_host_init(struct platform_device *pdev,
+			void *pp, u32 num_vectors);
+int dw_pcie_allocate_domains(struct dw_msi *msi);
+void dw_pcie_msi_init(struct dw_msi *msi);
+void dw_pcie_free_msi(struct dw_msi *msi);
+irqreturn_t dw_handle_msi_irq(struct dw_msi *msi);
+#endif /* _PCIE_DESIGNWARE_MSI_H */
diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
index 1b5aba1..e84298e 100644
--- a/drivers/pci/controller/dwc/pcie-designware.c
+++ b/drivers/pci/controller/dwc/pcie-designware.c
@@ -22,6 +22,7 @@
 
 #include "../../pci.h"
 #include "pcie-designware.h"
+#include "pcie-designware-msi.h"
 
 static const char * const dw_pcie_app_clks[DW_PCIE_NUM_APP_CLKS] = {
 	[DW_PCIE_DBI_CLK] = "dbi",
diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index ef84931..8b7fddf 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -232,12 +232,6 @@
 #define DEFAULT_DBI_ATU_OFFSET (0x3 << 20)
 #define DEFAULT_DBI_DMA_OFFSET PCIE_DMA_UNROLL_BASE
 
-#define MAX_MSI_IRQS			256
-#define MAX_MSI_IRQS_PER_CTRL		32
-#define MAX_MSI_CTRLS			(MAX_MSI_IRQS / MAX_MSI_IRQS_PER_CTRL)
-#define MSI_REG_CTRL_BLOCK_SIZE		12
-#define MSI_DEF_NUM_VECTORS		32
-
 /* Maximum number of inbound/outbound iATUs */
 #define MAX_IATU_IN			256
 #define MAX_IATU_OUT			256
@@ -319,7 +313,6 @@ struct dw_pcie_host_ops {
 };
 
 struct dw_pcie_rp {
-	bool			has_msi_ctrl:1;
 	bool			cfg0_io_shared:1;
 	u64			cfg0_base;
 	void __iomem		*va_cfg0_base;
@@ -329,16 +322,10 @@ struct dw_pcie_rp {
 	u32			io_size;
 	int			irq;
 	const struct dw_pcie_host_ops *ops;
-	int			msi_irq[MAX_MSI_CTRLS];
-	struct irq_domain	*irq_domain;
-	struct irq_domain	*msi_domain;
-	dma_addr_t		msi_data;
-	struct irq_chip		*msi_irq_chip;
 	u32			num_vectors;
-	u32			irq_mask[MAX_MSI_CTRLS];
+	struct dw_msi		*msi;
 	struct pci_host_bridge  *bridge;
 	raw_spinlock_t		lock;
-	DECLARE_BITMAP(msi_irq_in_use, MAX_MSI_IRQS);
 	bool			use_atu_msg;
 	int			msg_atu_index;
 	struct resource		*msg_res;
@@ -639,19 +626,12 @@ static inline enum dw_pcie_ltssm dw_pcie_get_ltssm(struct dw_pcie *pci)
 }
 
 #ifdef CONFIG_PCIE_DW_HOST
-irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp);
 int dw_pcie_setup_rc(struct dw_pcie_rp *pp);
 int dw_pcie_host_init(struct dw_pcie_rp *pp);
 void dw_pcie_host_deinit(struct dw_pcie_rp *pp);
-int dw_pcie_allocate_domains(struct dw_pcie_rp *pp);
 void __iomem *dw_pcie_own_conf_map_bus(struct pci_bus *bus, unsigned int devfn,
 				       int where);
 #else
-static inline irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp)
-{
-	return IRQ_NONE;
-}
-
 static inline int dw_pcie_setup_rc(struct dw_pcie_rp *pp)
 {
 	return 0;
@@ -666,10 +646,6 @@ static inline void dw_pcie_host_deinit(struct dw_pcie_rp *pp)
 {
 }
 
-static inline int dw_pcie_allocate_domains(struct dw_pcie_rp *pp)
-{
-	return 0;
-}
 static inline void __iomem *dw_pcie_own_conf_map_bus(struct pci_bus *bus,
 						     unsigned int devfn,
 						     int where)
diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c
index d99e12f..6139330 100644
--- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c
+++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c
@@ -16,6 +16,7 @@
 
 #include "../../pci.h"
 #include "pcie-designware.h"
+#include "pcie-designware-msi.h"
 
 /* Renesas-specific */
 /* PCIe Mode Setting Register 0 */
diff --git a/drivers/pci/controller/dwc/pcie-tegra194.c b/drivers/pci/controller/dwc/pcie-tegra194.c
index 804341b..f415fa1 100644
--- a/drivers/pci/controller/dwc/pcie-tegra194.c
+++ b/drivers/pci/controller/dwc/pcie-tegra194.c
@@ -34,6 +34,7 @@
 #include <soc/tegra/bpmp.h>
 #include <soc/tegra/bpmp-abi.h>
 #include "../../pci.h"
+#include "pcie-designware-msi.h"
 
 #define TEGRA194_DWC_IP_VER			0x490A
 #define TEGRA234_DWC_IP_VER			0x562A
@@ -2407,6 +2408,7 @@ static int tegra_pcie_dw_resume_early(struct device *dev)
 static void tegra_pcie_dw_shutdown(struct platform_device *pdev)
 {
 	struct tegra_pcie_dw *pcie = platform_get_drvdata(pdev);
+	struct dw_msi *msi;
 
 	if (pcie->of_data->mode == DW_PCIE_RC_TYPE) {
 		if (!pcie->link_state)
@@ -2415,9 +2417,10 @@ static void tegra_pcie_dw_shutdown(struct platform_device *pdev)
 		debugfs_remove_recursive(pcie->debugfs);
 		tegra_pcie_downstream_dev_to_D0(pcie);
 
+		msi = pcie->pci.pp.msi;
 		disable_irq(pcie->pci.pp.irq);
 		if (IS_ENABLED(CONFIG_PCI_MSI))
-			disable_irq(pcie->pci.pp.msi_irq[0]);
+			disable_irq(msi->msi_irq[0]);
 
 		tegra_pcie_dw_pme_turnoff(pcie);
 		tegra_pcie_unconfig_controller(pcie);
-- 
2.7.4





[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux