diff options
| -rw-r--r-- | drivers/pci/ide.c | 117 | ||||
| -rw-r--r-- | include/linux/pci-ide.h | 32 | ||||
| -rw-r--r-- | include/linux/pci.h | 5 |
3 files changed, 145 insertions, 9 deletions
diff --git a/drivers/pci/ide.c b/drivers/pci/ide.c index da5b1acccbb4..2e0856a4307b 100644 --- a/drivers/pci/ide.c +++ b/drivers/pci/ide.c @@ -155,8 +155,11 @@ struct pci_ide *pci_ide_stream_alloc(struct pci_dev *pdev) { /* EP, RP, + HB Stream allocation */ struct stream_index __stream[PCI_IDE_HB + 1]; + struct pci_bus_region pref_assoc = { 0, -1 }; + struct pci_bus_region mem_assoc = { 0, -1 }; + struct resource *mem, *pref; struct pci_host_bridge *hb; - struct pci_dev *rp; + struct pci_dev *rp, *br; int num_vf, rid_end; if (!pci_is_pcie(pdev)) @@ -197,6 +200,21 @@ struct pci_ide *pci_ide_stream_alloc(struct pci_dev *pdev) else rid_end = pci_dev_id(pdev); + br = pci_upstream_bridge(pdev); + if (!br) + return NULL; + + /* + * Check if the device consumes memory and/or prefetch-memory. Setup + * downstream address association ranges for each. + */ + mem = pci_resource_n(br, PCI_BRIDGE_MEM_WINDOW); + pref = pci_resource_n(br, PCI_BRIDGE_PREF_MEM_WINDOW); + if (resource_assigned(mem)) + pcibios_resource_to_bus(br->bus, &mem_assoc, mem); + if (resource_assigned(pref)) + pcibios_resource_to_bus(br->bus, &pref_assoc, pref); + *ide = (struct pci_ide) { .pdev = pdev, .partner = { @@ -204,11 +222,16 @@ struct pci_ide *pci_ide_stream_alloc(struct pci_dev *pdev) .rid_start = pci_dev_id(rp), .rid_end = pci_dev_id(rp), .stream_index = no_free_ptr(ep_stream)->stream_index, + /* Disable upstream address association */ + .mem_assoc = { 0, -1 }, + .pref_assoc = { 0, -1 }, }, [PCI_IDE_RP] = { .rid_start = pci_dev_id(pdev), .rid_end = rid_end, .stream_index = no_free_ptr(rp_stream)->stream_index, + .mem_assoc = mem_assoc, + .pref_assoc = pref_assoc, }, }, .host_bridge_stream = no_free_ptr(hb_stream)->stream_index, @@ -385,6 +408,63 @@ static void set_ide_sel_ctl(struct pci_dev *pdev, struct pci_ide *ide, pci_write_config_dword(pdev, pos + PCI_IDE_SEL_CTL, val); } +#define SEL_ADDR1_LOWER GENMASK(31, 20) +#define SEL_ADDR_UPPER GENMASK_ULL(63, 32) +#define PREP_PCI_IDE_SEL_ADDR1(base, limit) \ + (FIELD_PREP(PCI_IDE_SEL_ADDR_1_VALID, 1) | \ + FIELD_PREP(PCI_IDE_SEL_ADDR_1_BASE_LOW, \ + FIELD_GET(SEL_ADDR1_LOWER, (base))) | \ + FIELD_PREP(PCI_IDE_SEL_ADDR_1_LIMIT_LOW, \ + FIELD_GET(SEL_ADDR1_LOWER, (limit)))) + +static void mem_assoc_to_regs(struct pci_bus_region *region, + struct pci_ide_regs *regs, int idx) +{ + /* convert to u64 range for bitfield size checks */ + struct range r = { region->start, region->end }; + + regs->addr[idx].assoc1 = PREP_PCI_IDE_SEL_ADDR1(r.start, r.end); + regs->addr[idx].assoc2 = FIELD_GET(SEL_ADDR_UPPER, r.end); + regs->addr[idx].assoc3 = FIELD_GET(SEL_ADDR_UPPER, r.start); +} + +/** + * pci_ide_stream_to_regs() - convert IDE settings to association register values + * @pdev: PCIe device object for either a Root Port or Endpoint Partner Port + * @ide: registered IDE settings descriptor + * @regs: output register values + */ +static void pci_ide_stream_to_regs(struct pci_dev *pdev, struct pci_ide *ide, + struct pci_ide_regs *regs) +{ + struct pci_ide_partner *settings = pci_ide_to_settings(pdev, ide); + int assoc_idx = 0; + + memset(regs, 0, sizeof(*regs)); + + if (!settings) + return; + + regs->rid1 = FIELD_PREP(PCI_IDE_SEL_RID_1_LIMIT, settings->rid_end); + + regs->rid2 = FIELD_PREP(PCI_IDE_SEL_RID_2_VALID, 1) | + FIELD_PREP(PCI_IDE_SEL_RID_2_BASE, settings->rid_start) | + FIELD_PREP(PCI_IDE_SEL_RID_2_SEG, pci_ide_domain(pdev)); + + if (pdev->nr_ide_mem && pci_bus_region_size(&settings->mem_assoc)) { + mem_assoc_to_regs(&settings->mem_assoc, regs, assoc_idx); + assoc_idx++; + } + + if (pdev->nr_ide_mem > assoc_idx && + pci_bus_region_size(&settings->pref_assoc)) { + mem_assoc_to_regs(&settings->pref_assoc, regs, assoc_idx); + assoc_idx++; + } + + regs->nr_addr = assoc_idx; +} + /** * pci_ide_stream_setup() - program settings to Selective IDE Stream registers * @pdev: PCIe device object for either a Root Port or Endpoint Partner Port @@ -398,22 +478,34 @@ static void set_ide_sel_ctl(struct pci_dev *pdev, struct pci_ide *ide, void pci_ide_stream_setup(struct pci_dev *pdev, struct pci_ide *ide) { struct pci_ide_partner *settings = pci_ide_to_settings(pdev, ide); + struct pci_ide_regs regs; int pos; - u32 val; if (!settings) return; + pci_ide_stream_to_regs(pdev, ide, ®s); + pos = sel_ide_offset(pdev, settings); - val = FIELD_PREP(PCI_IDE_SEL_RID_1_LIMIT, settings->rid_end); - pci_write_config_dword(pdev, pos + PCI_IDE_SEL_RID_1, val); + pci_write_config_dword(pdev, pos + PCI_IDE_SEL_RID_1, regs.rid1); + pci_write_config_dword(pdev, pos + PCI_IDE_SEL_RID_2, regs.rid2); - val = FIELD_PREP(PCI_IDE_SEL_RID_2_VALID, 1) | - FIELD_PREP(PCI_IDE_SEL_RID_2_BASE, settings->rid_start) | - FIELD_PREP(PCI_IDE_SEL_RID_2_SEG, pci_ide_domain(pdev)); + for (int i = 0; i < regs.nr_addr; i++) { + pci_write_config_dword(pdev, pos + PCI_IDE_SEL_ADDR_1(i), + regs.addr[i].assoc1); + pci_write_config_dword(pdev, pos + PCI_IDE_SEL_ADDR_2(i), + regs.addr[i].assoc2); + pci_write_config_dword(pdev, pos + PCI_IDE_SEL_ADDR_3(i), + regs.addr[i].assoc3); + } - pci_write_config_dword(pdev, pos + PCI_IDE_SEL_RID_2, val); + /* clear extra unused address association blocks */ + for (int i = regs.nr_addr; i < pdev->nr_ide_mem; i++) { + pci_write_config_dword(pdev, pos + PCI_IDE_SEL_ADDR_1(i), 0); + pci_write_config_dword(pdev, pos + PCI_IDE_SEL_ADDR_2(i), 0); + pci_write_config_dword(pdev, pos + PCI_IDE_SEL_ADDR_3(i), 0); + } /* * Setup control register early for devices that expect @@ -436,7 +528,7 @@ EXPORT_SYMBOL_GPL(pci_ide_stream_setup); void pci_ide_stream_teardown(struct pci_dev *pdev, struct pci_ide *ide) { struct pci_ide_partner *settings = pci_ide_to_settings(pdev, ide); - int pos; + int pos, i; if (!settings) return; @@ -444,6 +536,13 @@ void pci_ide_stream_teardown(struct pci_dev *pdev, struct pci_ide *ide) pos = sel_ide_offset(pdev, settings); pci_write_config_dword(pdev, pos + PCI_IDE_SEL_CTL, 0); + + for (i = 0; i < pdev->nr_ide_mem; i++) { + pci_write_config_dword(pdev, pos + PCI_IDE_SEL_ADDR_1(i), 0); + pci_write_config_dword(pdev, pos + PCI_IDE_SEL_ADDR_2(i), 0); + pci_write_config_dword(pdev, pos + PCI_IDE_SEL_ADDR_3(i), 0); + } + pci_write_config_dword(pdev, pos + PCI_IDE_SEL_RID_2, 0); pci_write_config_dword(pdev, pos + PCI_IDE_SEL_RID_1, 0); settings->setup = 0; diff --git a/include/linux/pci-ide.h b/include/linux/pci-ide.h index d0f10f3c89fc..93194338e4d0 100644 --- a/include/linux/pci-ide.h +++ b/include/linux/pci-ide.h @@ -28,22 +28,54 @@ enum pci_ide_partner_select { * @rid_start: Partner Port Requester ID range start * @rid_end: Partner Port Requester ID range end * @stream_index: Selective IDE Stream Register Block selection + * @mem_assoc: PCI bus memory address association for targeting peer partner + * @pref_assoc: PCI bus prefetchable memory address association for + * targeting peer partner * @default_stream: Endpoint uses this stream for all upstream TLPs regardless of * address and RID association registers * @setup: flag to track whether to run pci_ide_stream_teardown() for this * partner slot * @enable: flag whether to run pci_ide_stream_disable() for this partner slot + * + * By default, pci_ide_stream_alloc() initializes @mem_assoc and @pref_assoc + * with the immediate ancestor downstream port memory ranges (i.e. Type 1 + * Configuration Space Header values). Caller may zero size ({0, -1}) the range + * to drop it from consideration at pci_ide_stream_setup() time. */ struct pci_ide_partner { u16 rid_start; u16 rid_end; u8 stream_index; + struct pci_bus_region mem_assoc; + struct pci_bus_region pref_assoc; unsigned int default_stream:1; unsigned int setup:1; unsigned int enable:1; }; /** + * struct pci_ide_regs - Hardware register association settings for Selective + * IDE Streams + * @rid1: IDE RID Association Register 1 + * @rid2: IDE RID Association Register 2 + * @addr: Up to two address association blocks (IDE Address Association Register + * 1 through 3) for MMIO and prefetchable MMIO + * @nr_addr: Number of address association blocks initialized + * + * See pci_ide_stream_to_regs() + */ +struct pci_ide_regs { + u32 rid1; + u32 rid2; + struct { + u32 assoc1; + u32 assoc2; + u32 assoc3; + } addr[2]; + int nr_addr; +}; + +/** * struct pci_ide - PCIe Selective IDE Stream descriptor * @pdev: PCIe Endpoint in the pci_ide_partner pair * @partner: per-partner settings diff --git a/include/linux/pci.h b/include/linux/pci.h index 2c8dbae4916c..ba39ca78b382 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -870,6 +870,11 @@ struct pci_bus_region { pci_bus_addr_t end; }; +static inline pci_bus_addr_t pci_bus_region_size(const struct pci_bus_region *region) +{ + return region->end - region->start + 1; +} + struct pci_dynids { spinlock_t lock; /* Protects list, index */ struct list_head list; /* For IDs added at runtime */ |
