[PATCH v2 5/6] sparc64: add M3000 Oberon PCIe support
flat view
WARM1d
SUPERSEDED
From: Magnus Lindholm <linmag7@gmail.com>
Date: 2026-10-11 19:57:59
Also in:
linux-serial, lkml, sparclinux
Subsystem:
sparc + ultrasparc (sparc/sparc64), the rest · Maintainers:
"David S. Miller", Andreas Larsson, Linus Torvalds
Revision v2 of 3 in this series.
Revisions (3)
- v1 [diff vs current]
- v2 current
- v3 [diff vs current]
Recognize Oberon while preserving firmware link setup. Flush DVMA translations and changed noncoherent IOTSB cache lines, and route legacy interrupts using the bridge's ten-bit target ID. Cache the bridge type and share target encoding. Leave MSI queues uninitialized so the existing architecture path uses legacy interrupts even with CONFIG_PCI_MSI=y; native bridge error handling is not provided. Signed-off-by: Magnus Lindholm <linmag7@gmail.com> --- arch/sparc/Kconfig | 10 +++++++ arch/sparc/include/asm/iommu_64.h | 1 + arch/sparc/include/asm/irq_64.h | 1 + arch/sparc/kernel/iommu.c | 44 +++++++++++++++++++++++++++ arch/sparc/kernel/irq_64.c | 50 ++++++++++++++++++++++++------- arch/sparc/kernel/pci_fire.c | 31 +++++++++++++++++-- arch/sparc/kernel/pci_impl.h | 1 + arch/sparc/kernel/prom_irqtrans.c | 7 +++++ 8 files changed, 132 insertions(+), 13 deletions(-)
diff --git a/arch/sparc/Kconfig b/arch/sparc/Kconfig
index 1263150a5702..96aa140815fe 100644
--- a/arch/sparc/Kconfig
+++ b/arch/sparc/Kconfig@@ -387,6 +387,16 @@ endif endmenu menu "Bus options (PCI etc.)" + +config SPARC64_OBERON + bool "Fujitsu M3000 Oberon PCIe bridge" + depends on SPARC64_VII && PCI + help + Support Oberon enumeration, translated DMA and legacy interrupts. + Firmware link configuration is preserved. MSI and native bridge + error interrupt handling are not supported on Oberon. + Enable this option for the M3000 on-board network and disk devices. + config SBUS bool default y
diff --git a/arch/sparc/include/asm/iommu_64.h b/arch/sparc/include/asm/iommu_64.h
index 0ef6dedf747e..abd7bb0fa615 100644
--- a/arch/sparc/include/asm/iommu_64.h
+++ b/arch/sparc/include/asm/iommu_64.h@@ -62,6 +62,7 @@ struct iommu { unsigned long iommu_tsbbase; unsigned long iommu_flush; unsigned long iommu_flushinv; + unsigned long iommu_cache_flush; /* Oberon IOTSB cache */ unsigned long iommu_tags; unsigned long iommu_ctxflush; unsigned long write_complete_reg;
diff --git a/arch/sparc/include/asm/irq_64.h b/arch/sparc/include/asm/irq_64.h
index 8c4c0c87f998..c3e88f2f8e80 100644
--- a/arch/sparc/include/asm/irq_64.h
+++ b/arch/sparc/include/asm/irq_64.h@@ -45,6 +45,7 @@ void irq_install_pre_handler(int irq, void *arg1, void *arg2); #define irq_canonicalize(irq) (irq) unsigned int build_irq(int inofixup, unsigned long iclr, unsigned long imap); +unsigned int oberon_build_irq(int inofixup, unsigned long iclr, unsigned long imap); unsigned int sun4v_build_irq(u32 devhandle, unsigned int devino); unsigned int sun4v_build_virq(u32 devhandle, unsigned int devino); unsigned int sun4v_build_msi(u32 devhandle, unsigned int *irq_p,
diff --git a/arch/sparc/kernel/iommu.c b/arch/sparc/kernel/iommu.c
index 7613ab0ffb89..9132f0535ce4 100644
--- a/arch/sparc/kernel/iommu.c
+++ b/arch/sparc/kernel/iommu.c@@ -52,6 +52,8 @@ static void iommu_flushall(struct iommu_map_table *iommu_map_table) struct iommu *iommu = container_of(iommu_map_table, struct iommu, tbl); if (iommu->iommu_flushinv) { iommu_write(iommu->iommu_flushinv, ~(u64)0); + if (iommu->iommu_cache_flush) + (void)iommu_read(iommu->write_complete_reg); } else { unsigned long tag; int entry;
@@ -67,6 +69,30 @@ static void iommu_flushall(struct iommu_map_table *iommu_map_table) } } +/* + * Oberon's noncoherent IOTSB needs each changed 64-byte cache line flushed + * along with its DVMA translations. Call only for device-inactive mappings. + */ +static void iommu_sync_mapping(struct iommu *iommu, iopte_t *base, + unsigned long npages) +{ + unsigned long slot, i; + + if (!iommu->iommu_cache_flush || !npages) + return; + slot = base - iommu->page_table; + /* Publish IOPTE stores before invalidating the hardware caches. */ + wmb(); + for (i = 0; i < npages; i++, slot++) { + iommu_write(iommu->iommu_flush, + iommu->tbl.table_map_base + (slot << IO_PAGE_SHIFT)); + if ((slot & 7) == 7 || i == npages - 1) + iommu_write(iommu->iommu_cache_flush, + __pa(iommu->page_table + slot)); + } + (void)iommu_read(iommu->write_complete_reg); +} + #define IOPTE_CONSISTENT(CTX) \ (IOPTE_VALID | IOPTE_CACHE | \ (((CTX) << 47) & IOPTE_CONTEXT))
@@ -240,6 +266,8 @@ static void *dma_4u_alloc_coherent(struct device *dev, size_t size, first_page += IO_PAGE_SIZE; } + iommu_sync_mapping(iommu, iopte - (size >> IO_PAGE_SHIFT), + size >> IO_PAGE_SHIFT); return ret; }
@@ -253,6 +281,15 @@ static void dma_4u_free_coherent(struct device *dev, size_t size, npages = IO_PAGE_ALIGN(size) >> IO_PAGE_SHIFT; iommu = dev->archdata.iommu; + if (iommu->iommu_cache_flush) { + iopte_t *base = iommu->page_table + + ((dvma - iommu->tbl.table_map_base) >> IO_PAGE_SHIFT); + unsigned long i; + + for (i = 0; i < npages; i++) + iopte_make_dummy(iommu, base + i); + iommu_sync_mapping(iommu, base, npages); + } iommu_tbl_range_free(&iommu->tbl, dvma, npages, IOMMU_ERROR_CODE); order = get_order(size);
@@ -316,6 +353,7 @@ static dma_addr_t dma_4u_map_phys(struct device *dev, phys_addr_t phys, for (i = 0; i < npages; i++, base++, phys += IO_PAGE_SIZE) iopte_val(*base) = iopte_protection | phys; + iommu_sync_mapping(iommu, base - npages, npages); return ret;
@@ -432,6 +470,7 @@ static void dma_4u_unmap_phys(struct device *dev, dma_addr_t bus_addr, /* Step 2: Clear out TSB entries. */ for (i = 0; i < npages; i++) iopte_make_dummy(iommu, base + i); + iommu_sync_mapping(iommu, base, npages); iommu_free_ctx(iommu, ctx); spin_unlock_irqrestore(&iommu->lock, flags);
@@ -523,6 +562,9 @@ static int dma_4u_map_sg(struct device *dev, struct scatterlist *sglist, paddr += IO_PAGE_SIZE; } + iommu_sync_mapping(iommu, iommu->page_table + entry, + base - (iommu->page_table + entry)); + /* If we are in an open segment, try merging */ if (segstart != s) { /* We cannot merge if:
@@ -577,6 +619,7 @@ static int dma_4u_map_sg(struct device *dev, struct scatterlist *sglist, for (j = 0; j < npages; j++) iopte_make_dummy(iommu, base + j); + iommu_sync_mapping(iommu, base, npages); iommu_tbl_range_free(&iommu->tbl, vaddr, npages, IOMMU_ERROR_CODE);
@@ -653,6 +696,7 @@ static void dma_4u_unmap_sg(struct device *dev, struct scatterlist *sglist, for (i = 0; i < npages; i++) iopte_make_dummy(iommu, base + i); + iommu_sync_mapping(iommu, base, npages); iommu_tbl_range_free(&iommu->tbl, dma_handle, npages, IOMMU_ERROR_CODE);
diff --git a/arch/sparc/kernel/irq_64.c b/arch/sparc/kernel/irq_64.c
index 3f55c69d5f3b..b0f3b26656d4 100644
--- a/arch/sparc/kernel/irq_64.c
+++ b/arch/sparc/kernel/irq_64.c@@ -206,6 +206,7 @@ struct irq_handler_data { struct ino_bucket bucket; unsigned long iclr; unsigned long imap; + bool oberon; }; static inline unsigned int irq_data_to_handle(struct irq_data *data)
@@ -364,6 +365,16 @@ static int irq_choose_cpu(unsigned int irq, const struct cpumask *affinity) real_hard_smp_processor_id() #endif +/* Oberon IMAP destination is a ten-bit interrupt target ID. */ +#define OBERON_IMAP_TID_SHIFT 21 +#define OBERON_IMAP_TID_MASK (0x3ffUL << OBERON_IMAP_TID_SHIFT) + +static unsigned long oberon_imap_target(unsigned long val, unsigned int cpuid) +{ + return (val & ~OBERON_IMAP_TID_MASK) | + ((cpuid << OBERON_IMAP_TID_SHIFT) & OBERON_IMAP_TID_MASK); +} + static void sun4u_irq_enable(struct irq_data *data) { struct irq_handler_data *handler_data;
@@ -377,12 +388,16 @@ static void sun4u_irq_enable(struct irq_data *data) irq_data_get_affinity_mask(data)); imap = handler_data->imap; - tid = sun4u_compute_tid(imap, cpuid); - val = upa_readq(imap); - val &= ~(IMAP_TID_UPA | IMAP_TID_JBUS | - IMAP_AID_SAFARI | IMAP_NID_SAFARI); - val |= tid | IMAP_VALID; + if (handler_data->oberon) { + val = oberon_imap_target(val, cpuid); + } else { + tid = sun4u_compute_tid(imap, cpuid); + val &= ~(IMAP_TID_UPA | IMAP_TID_JBUS | + IMAP_AID_SAFARI | IMAP_NID_SAFARI); + val |= tid; + } + val |= IMAP_VALID; upa_writeq(val, imap); upa_writeq(ICLR_IDLE, handler_data->iclr); }
@@ -401,12 +416,16 @@ static int sun4u_set_affinity(struct irq_data *data, cpuid = irq_choose_cpu(data->irq, mask); imap = handler_data->imap; - tid = sun4u_compute_tid(imap, cpuid); - val = upa_readq(imap); - val &= ~(IMAP_TID_UPA | IMAP_TID_JBUS | - IMAP_AID_SAFARI | IMAP_NID_SAFARI); - val |= tid | IMAP_VALID; + if (handler_data->oberon) { + val = oberon_imap_target(val, cpuid); + } else { + tid = sun4u_compute_tid(imap, cpuid); + val &= ~(IMAP_TID_UPA | IMAP_TID_JBUS | + IMAP_AID_SAFARI | IMAP_NID_SAFARI); + val |= tid; + } + val |= IMAP_VALID; upa_writeq(val, imap); upa_writeq(ICLR_IDLE, handler_data->iclr); }
@@ -642,6 +661,17 @@ unsigned int build_irq(int inofixup, unsigned long iclr, unsigned long imap) return irq; } +unsigned int oberon_build_irq(int inofixup, unsigned long iclr, + unsigned long imap) +{ + unsigned int irq = build_irq(inofixup, iclr, imap); + struct irq_handler_data *data = irq_get_handler_data(irq); + + if (data) + data->oberon = true; + return irq; +} + static unsigned int sun4v_build_common(u32 devhandle, unsigned int devino, void (*handler_data_init)(struct irq_handler_data *data, u32 devhandle, unsigned int devino),
diff --git a/arch/sparc/kernel/pci_fire.c b/arch/sparc/kernel/pci_fire.c
index c35a8e3c1eae..1921996298af 100644
--- a/arch/sparc/kernel/pci_fire.c
+++ b/arch/sparc/kernel/pci_fire.c@@ -46,11 +46,21 @@ static int pci_fire_pbm_iommu_init(struct pci_pbm_info *pbm) iommu->iommu_tsbbase = pbm->pbm_regs + FIRE_IOMMU_TSBBASE; iommu->iommu_flush = pbm->pbm_regs + FIRE_IOMMU_FLUSH; iommu->iommu_flushinv = pbm->pbm_regs + FIRE_IOMMU_FLUSHINV; + if (pbm->is_oberon) { + /* + * 0x40100 flushes an IOTSB cache line by physical address; + * 0x40010 flushes a translated DVMA page. + */ + iommu->iommu_flush = pbm->pbm_regs + 0x40010UL; + iommu->iommu_cache_flush = pbm->pbm_regs + 0x40100UL; + iommu->write_complete_reg = iommu->iommu_control; + } /* We use the main control/status register of FIRE as the write * completion register. */ - iommu->write_complete_reg = pbm->controller_regs + 0x410000UL; + if (!iommu->iommu_cache_flush) + iommu->write_complete_reg = pbm->controller_regs + 0x410000UL; /* * Invalidate TLB Entries.
@@ -62,6 +72,8 @@ static int pci_fire_pbm_iommu_init(struct pci_pbm_info *pbm) if (err) return err; + /* Make initialized IOPTEs visible before publishing the table base. */ + wmb(); upa_writeq(__pa(iommu->page_table) | 0x7UL, iommu->iommu_tsbbase); control = upa_readq(iommu->iommu_control);
@@ -70,6 +82,10 @@ static int pci_fire_pbm_iommu_init(struct pci_pbm_info *pbm) 0x00000002 /* Bypass enable */ | 0x00000001 /* Translation enable */); upa_writeq(control, iommu->iommu_control); + if (iommu->iommu_cache_flush) { + upa_writeq(~(u64)0, iommu->iommu_flushinv); + (void)upa_readq(iommu->iommu_control); + } return 0; }
@@ -419,6 +435,7 @@ static int pci_fire_pbm_init(struct pci_pbm_info *pbm, int err; pbm->numa_node = NUMA_NO_NODE; + pbm->is_oberon = of_device_is_compatible(dp, "pciex108e,80f8"); pbm->pci_ops = &sun4u_pci_ops; pbm->config_space_reg_bits = 12;
@@ -439,13 +456,18 @@ static int pci_fire_pbm_init(struct pci_pbm_info *pbm, pci_get_pbm_props(pbm); - pci_fire_hw_init(pbm); + if (pbm->is_oberon) + pr_info("%s: experimental Oberon; preserving firmware link setup\n", + pbm->name); + else + pci_fire_hw_init(pbm); err = pci_fire_pbm_iommu_init(pbm); if (err) return err; - pci_fire_msi_init(pbm); + if (!pbm->is_oberon) + pci_fire_msi_init(pbm); pbm->pci_bus = pci_scan_one_pbm(pbm, &op->dev);
@@ -505,6 +527,9 @@ static const struct of_device_id fire_match[] = { .name = "pci", .compatible = "pciex108e,80f0", }, +#ifdef CONFIG_SPARC64_OBERON + { .name = "pci", .compatible = "pciex108e,80f8" }, +#endif {}, };
diff --git a/arch/sparc/kernel/pci_impl.h b/arch/sparc/kernel/pci_impl.h
index 83718876f1d4..92505fcc3cf3 100644
--- a/arch/sparc/kernel/pci_impl.h
+++ b/arch/sparc/kernel/pci_impl.h@@ -117,6 +117,7 @@ struct pci_pbm_info { /* State of 66MHz capabilities on this PBM. */ int is_66mhz_capable; int all_devs_66mhz; + bool is_oberon; #ifdef CONFIG_PCI_MSI /* MSI info. */
diff --git a/arch/sparc/kernel/prom_irqtrans.c b/arch/sparc/kernel/prom_irqtrans.c
index 5752bfd73ac0..60fd6b0f47e1 100644
--- a/arch/sparc/kernel/prom_irqtrans.c
+++ b/arch/sparc/kernel/prom_irqtrans.c@@ -481,6 +481,7 @@ static void __init pci_sun4v_irq_trans_init(struct device_node *dp) struct fire_irq_data { unsigned long pbm_regs; u32 portid; + bool oberon; }; #define FIRE_IMAP_BASE 0x001000
@@ -537,6 +538,8 @@ static unsigned int fire_irq_build(struct device_node *dp, */ ino |= (irq_data->portid << 6); ino -= int_ctrlr; + if (irq_data->oberon) + return oberon_build_irq(ino, iclr, imap); return build_irq(ino, iclr, imap); }
@@ -553,6 +556,7 @@ static void __init fire_irq_trans_init(struct device_node *dp) regs = of_get_property(dp, "reg", NULL); dp->irq_trans->data = irq_data; + irq_data->oberon = of_device_is_compatible(dp, "pciex108e,80f8"); irq_data->pbm_regs = regs[0].phys_addr; irq_data->portid = of_getintprop_default(dp, "portid", 0); }
@@ -778,6 +782,9 @@ static struct irq_trans __initdata pci_irq_trans_table[] = { { "pci108e,a801", tomatillo_irq_trans_init }, { "SUNW,sun4v-pci", pci_sun4v_irq_trans_init }, { "pciex108e,80f0", fire_irq_trans_init }, +#ifdef CONFIG_SPARC64_OBERON + { "pciex108e,80f8", fire_irq_trans_init }, +#endif }; #endif
--
2.43.0