| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright(c) 2024-2025 Intel Corporation. All rights reserved. */ |
| |
| /* PCIe r7.0 section 6.33 Integrity & Data Encryption (IDE) */ |
| |
| #define dev_fmt(fmt) "PCI/IDE: " fmt |
| #include <linux/bitfield.h> |
| #include <linux/bitops.h> |
| #include <linux/pci.h> |
| #include <linux/pci-ide.h> |
| #include <linux/pci_regs.h> |
| #include <linux/slab.h> |
| #include <linux/sysfs.h> |
| #include <linux/tsm.h> |
| |
| #include "pci.h" |
| |
| static int __sel_ide_offset(u16 ide_cap, u8 nr_link_ide, u8 stream_index, |
| u8 nr_ide_mem) |
| { |
| u32 offset = ide_cap + PCI_IDE_LINK_STREAM_0 + |
| nr_link_ide * PCI_IDE_LINK_BLOCK_SIZE; |
| |
| /* |
| * Assume a constant number of address association resources per stream |
| * index |
| */ |
| return offset + stream_index * PCI_IDE_SEL_BLOCK_SIZE(nr_ide_mem); |
| } |
| |
| static int sel_ide_offset(struct pci_dev *pdev, |
| struct pci_ide_partner *settings) |
| { |
| return __sel_ide_offset(pdev->ide_cap, pdev->nr_link_ide, |
| settings->stream_index, pdev->nr_ide_mem); |
| } |
| |
| static bool reserve_stream_index(struct pci_dev *pdev, u8 idx) |
| { |
| int ret; |
| |
| ret = ida_alloc_range(&pdev->ide_stream_ida, idx, idx, GFP_KERNEL); |
| return ret >= 0; |
| } |
| |
| static bool reserve_stream_id(struct pci_host_bridge *hb, u8 id) |
| { |
| int ret; |
| |
| ret = ida_alloc_range(&hb->ide_stream_ids_ida, id, id, GFP_KERNEL); |
| return ret >= 0; |
| } |
| |
| static bool claim_stream(struct pci_host_bridge *hb, u8 stream_id, |
| struct pci_dev *pdev, u8 stream_idx) |
| { |
| dev_info(&hb->dev, "Stream ID %d active at init\n", stream_id); |
| if (!reserve_stream_id(hb, stream_id)) { |
| dev_info(&hb->dev, "Failed to claim %s Stream ID %d\n", |
| stream_id == PCI_IDE_RESERVED_STREAM_ID ? "reserved" : |
| "active", |
| stream_id); |
| return false; |
| } |
| |
| /* No stream index to reserve in the Link IDE case */ |
| if (!pdev) |
| return true; |
| |
| if (!reserve_stream_index(pdev, stream_idx)) { |
| pci_info(pdev, "Failed to claim active Selective Stream %d\n", |
| stream_idx); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void pci_ide_init(struct pci_dev *pdev) |
| { |
| struct pci_host_bridge *hb = pci_find_host_bridge(pdev->bus); |
| u16 nr_link_ide, nr_ide_mem, nr_streams; |
| u16 ide_cap; |
| u32 val; |
| |
| /* |
| * Unconditionally init so that ida idle state is consistent with |
| * pdev->ide_cap. |
| */ |
| ida_init(&pdev->ide_stream_ida); |
| |
| if (!pci_is_pcie(pdev)) |
| return; |
| |
| ide_cap = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_IDE); |
| if (!ide_cap) |
| return; |
| |
| pci_read_config_dword(pdev, ide_cap + PCI_IDE_CAP, &val); |
| if ((val & PCI_IDE_CAP_SELECTIVE) == 0) |
| return; |
| |
| /* |
| * Require endpoint IDE capability to be paired with IDE Root Port IDE |
| * capability. |
| */ |
| if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ENDPOINT) { |
| struct pci_dev *rp = pcie_find_root_port(pdev); |
| |
| if (!rp->ide_cap) |
| return; |
| } |
| |
| pdev->ide_cfg = FIELD_GET(PCI_IDE_CAP_SEL_CFG, val); |
| pdev->ide_tee_limit = FIELD_GET(PCI_IDE_CAP_TEE_LIMITED, val); |
| |
| if (val & PCI_IDE_CAP_LINK) |
| nr_link_ide = 1 + FIELD_GET(PCI_IDE_CAP_LINK_TC_NUM, val); |
| else |
| nr_link_ide = 0; |
| |
| nr_ide_mem = 0; |
| nr_streams = 1 + FIELD_GET(PCI_IDE_CAP_SEL_NUM, val); |
| for (u16 i = 0; i < nr_streams; i++) { |
| int pos = __sel_ide_offset(ide_cap, nr_link_ide, i, nr_ide_mem); |
| int nr_assoc; |
| u32 val; |
| u8 id; |
| |
| pci_read_config_dword(pdev, pos + PCI_IDE_SEL_CAP, &val); |
| |
| /* |
| * Let's not entertain streams that do not have a constant |
| * number of address association blocks |
| */ |
| nr_assoc = FIELD_GET(PCI_IDE_SEL_CAP_ASSOC_NUM, val); |
| if (i && (nr_assoc != nr_ide_mem)) { |
| pci_info(pdev, "Unsupported Selective Stream %d capability, SKIP the rest\n", i); |
| nr_streams = i; |
| break; |
| } |
| |
| nr_ide_mem = nr_assoc; |
| |
| /* |
| * Claim Stream IDs and Selective Stream blocks that are already |
| * active on the device |
| */ |
| pci_read_config_dword(pdev, pos + PCI_IDE_SEL_CTL, &val); |
| id = FIELD_GET(PCI_IDE_SEL_CTL_ID, val); |
| if ((val & PCI_IDE_SEL_CTL_EN) && |
| !claim_stream(hb, id, pdev, i)) |
| return; |
| } |
| |
| /* Reserve link stream-ids that are already active on the device */ |
| for (u16 i = 0; i < nr_link_ide; ++i) { |
| int pos = ide_cap + PCI_IDE_LINK_STREAM_0 + i * PCI_IDE_LINK_BLOCK_SIZE; |
| u8 id; |
| |
| pci_read_config_dword(pdev, pos + PCI_IDE_LINK_CTL_0, &val); |
| id = FIELD_GET(PCI_IDE_LINK_CTL_ID, val); |
| if ((val & PCI_IDE_LINK_CTL_EN) && |
| !claim_stream(hb, id, NULL, -1)) |
| return; |
| } |
| |
| for (u16 i = 0; i < nr_streams; i++) { |
| int pos = __sel_ide_offset(ide_cap, nr_link_ide, i, nr_ide_mem); |
| |
| pci_read_config_dword(pdev, pos + PCI_IDE_SEL_CAP, &val); |
| if (val & PCI_IDE_SEL_CTL_EN) |
| continue; |
| val &= ~PCI_IDE_SEL_CTL_ID; |
| val |= FIELD_PREP(PCI_IDE_SEL_CTL_ID, PCI_IDE_RESERVED_STREAM_ID); |
| pci_write_config_dword(pdev, pos + PCI_IDE_SEL_CTL, val); |
| } |
| |
| for (u16 i = 0; i < nr_link_ide; ++i) { |
| int pos = ide_cap + PCI_IDE_LINK_STREAM_0 + |
| i * PCI_IDE_LINK_BLOCK_SIZE; |
| |
| pci_read_config_dword(pdev, pos, &val); |
| if (val & PCI_IDE_LINK_CTL_EN) |
| continue; |
| val &= ~PCI_IDE_LINK_CTL_ID; |
| val |= FIELD_PREP(PCI_IDE_LINK_CTL_ID, PCI_IDE_RESERVED_STREAM_ID); |
| pci_write_config_dword(pdev, pos, val); |
| } |
| |
| pdev->ide_cap = ide_cap; |
| pdev->nr_link_ide = nr_link_ide; |
| pdev->nr_sel_ide = nr_streams; |
| pdev->nr_ide_mem = nr_ide_mem; |
| } |
| |
| struct stream_index { |
| struct ida *ida; |
| u8 stream_index; |
| }; |
| |
| static void free_stream_index(struct stream_index *stream) |
| { |
| ida_free(stream->ida, stream->stream_index); |
| } |
| |
| DEFINE_FREE(free_stream, struct stream_index *, if (_T) free_stream_index(_T)) |
| static struct stream_index *alloc_stream_index(struct ida *ida, u16 max, |
| struct stream_index *stream) |
| { |
| int id; |
| |
| if (!max) |
| return NULL; |
| |
| id = ida_alloc_max(ida, max - 1, GFP_KERNEL); |
| if (id < 0) |
| return NULL; |
| |
| *stream = (struct stream_index) { |
| .ida = ida, |
| .stream_index = id, |
| }; |
| return stream; |
| } |
| |
| /** |
| * pci_ide_stream_alloc() - Reserve stream indices and probe for settings |
| * @pdev: IDE capable PCIe Endpoint Physical Function |
| * |
| * Retrieve the Requester ID range of @pdev for programming its Root |
| * Port IDE RID Association registers, and conversely retrieve the |
| * Requester ID of the Root Port for programming @pdev's IDE RID |
| * Association registers. |
| * |
| * Allocate a Selective IDE Stream Register Block instance per port. |
| * |
| * Allocate a platform stream resource from the associated host bridge. |
| * Retrieve stream association parameters for Requester ID range and |
| * address range restrictions for the stream. |
| */ |
| 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, *br; |
| int num_vf, rid_end; |
| |
| if (!pci_is_pcie(pdev)) |
| return NULL; |
| |
| if (pci_pcie_type(pdev) != PCI_EXP_TYPE_ENDPOINT) |
| return NULL; |
| |
| if (!pdev->ide_cap) |
| return NULL; |
| |
| struct pci_ide *ide __free(kfree) = kzalloc(sizeof(*ide), GFP_KERNEL); |
| if (!ide) |
| return NULL; |
| |
| hb = pci_find_host_bridge(pdev->bus); |
| struct stream_index *hb_stream __free(free_stream) = alloc_stream_index( |
| &hb->ide_stream_ida, hb->nr_ide_streams, &__stream[PCI_IDE_HB]); |
| if (!hb_stream) |
| return NULL; |
| |
| rp = pcie_find_root_port(pdev); |
| struct stream_index *rp_stream __free(free_stream) = alloc_stream_index( |
| &rp->ide_stream_ida, rp->nr_sel_ide, &__stream[PCI_IDE_RP]); |
| if (!rp_stream) |
| return NULL; |
| |
| struct stream_index *ep_stream __free(free_stream) = alloc_stream_index( |
| &pdev->ide_stream_ida, pdev->nr_sel_ide, &__stream[PCI_IDE_EP]); |
| if (!ep_stream) |
| return NULL; |
| |
| /* for SR-IOV case, cover all VFs */ |
| num_vf = pci_num_vf(pdev); |
| if (num_vf) |
| rid_end = PCI_DEVID(pci_iov_virtfn_bus(pdev, num_vf), |
| pci_iov_virtfn_devfn(pdev, num_vf)); |
| 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 = { |
| [PCI_IDE_EP] = { |
| .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, |
| .stream_id = -1, |
| }; |
| |
| return_ptr(ide); |
| } |
| EXPORT_SYMBOL_GPL(pci_ide_stream_alloc); |
| |
| /** |
| * pci_ide_stream_free() - unwind pci_ide_stream_alloc() |
| * @ide: idle IDE settings descriptor |
| * |
| * Free all of the stream index (register block) allocations acquired by |
| * pci_ide_stream_alloc(). The stream represented by @ide is assumed to |
| * be unregistered and not instantiated in any device. |
| */ |
| void pci_ide_stream_free(struct pci_ide *ide) |
| { |
| struct pci_dev *pdev = ide->pdev; |
| struct pci_dev *rp = pcie_find_root_port(pdev); |
| struct pci_host_bridge *hb = pci_find_host_bridge(pdev->bus); |
| |
| ida_free(&pdev->ide_stream_ida, ide->partner[PCI_IDE_EP].stream_index); |
| ida_free(&rp->ide_stream_ida, ide->partner[PCI_IDE_RP].stream_index); |
| ida_free(&hb->ide_stream_ida, ide->host_bridge_stream); |
| kfree(ide); |
| } |
| EXPORT_SYMBOL_GPL(pci_ide_stream_free); |
| |
| /** |
| * pci_ide_stream_release() - unwind and release an @ide context |
| * @ide: partially or fully registered IDE settings descriptor |
| * |
| * In support of automatic cleanup of IDE setup routines perform IDE |
| * teardown in expected reverse order of setup and with respect to which |
| * aspects of IDE setup have successfully completed. |
| * |
| * Be careful that setup order mirrors this shutdown order. Otherwise, |
| * open code releasing the IDE context. |
| */ |
| void pci_ide_stream_release(struct pci_ide *ide) |
| { |
| struct pci_dev *pdev = ide->pdev; |
| struct pci_dev *rp = pcie_find_root_port(pdev); |
| |
| if (ide->partner[PCI_IDE_RP].enable) |
| pci_ide_stream_disable(rp, ide); |
| |
| if (ide->partner[PCI_IDE_EP].enable) |
| pci_ide_stream_disable(pdev, ide); |
| |
| if (ide->tsm_dev) |
| tsm_ide_stream_unregister(ide); |
| |
| if (ide->partner[PCI_IDE_RP].setup) |
| pci_ide_stream_teardown(rp, ide); |
| |
| if (ide->partner[PCI_IDE_EP].setup) |
| pci_ide_stream_teardown(pdev, ide); |
| |
| if (ide->name) |
| pci_ide_stream_unregister(ide); |
| |
| pci_ide_stream_free(ide); |
| } |
| EXPORT_SYMBOL_GPL(pci_ide_stream_release); |
| |
| struct pci_ide_stream_id { |
| struct pci_host_bridge *hb; |
| u8 stream_id; |
| }; |
| |
| static struct pci_ide_stream_id * |
| request_stream_id(struct pci_host_bridge *hb, u8 stream_id, |
| struct pci_ide_stream_id *sid) |
| { |
| if (!reserve_stream_id(hb, stream_id)) |
| return NULL; |
| |
| *sid = (struct pci_ide_stream_id) { |
| .hb = hb, |
| .stream_id = stream_id, |
| }; |
| |
| return sid; |
| } |
| DEFINE_FREE(free_stream_id, struct pci_ide_stream_id *, |
| if (_T) ida_free(&_T->hb->ide_stream_ids_ida, _T->stream_id)) |
| |
| /** |
| * pci_ide_stream_register() - Prepare to activate an IDE Stream |
| * @ide: IDE settings descriptor |
| * |
| * After a Stream ID has been acquired for @ide, record the presence of |
| * the stream in sysfs. The expectation is that @ide is immutable while |
| * registered. |
| */ |
| int pci_ide_stream_register(struct pci_ide *ide) |
| { |
| struct pci_dev *pdev = ide->pdev; |
| struct pci_host_bridge *hb = pci_find_host_bridge(pdev->bus); |
| struct pci_ide_stream_id __sid; |
| u8 ep_stream, rp_stream; |
| int rc; |
| |
| if (ide->stream_id < 0 || ide->stream_id > U8_MAX) { |
| pci_err(pdev, "Setup fail: Invalid Stream ID: %d\n", ide->stream_id); |
| return -ENXIO; |
| } |
| |
| struct pci_ide_stream_id *sid __free(free_stream_id) = |
| request_stream_id(hb, ide->stream_id, &__sid); |
| if (!sid) { |
| pci_err(pdev, "Setup fail: Stream ID %d in use\n", ide->stream_id); |
| return -EBUSY; |
| } |
| |
| ep_stream = ide->partner[PCI_IDE_EP].stream_index; |
| rp_stream = ide->partner[PCI_IDE_RP].stream_index; |
| const char *name __free(kfree) = kasprintf(GFP_KERNEL, "stream%d.%d.%d", |
| ide->host_bridge_stream, |
| rp_stream, ep_stream); |
| if (!name) |
| return -ENOMEM; |
| |
| rc = sysfs_create_link(&hb->dev.kobj, &pdev->dev.kobj, name); |
| if (rc) |
| return rc; |
| |
| ide->name = no_free_ptr(name); |
| |
| /* Stream ID reservation recorded in @ide is now successfully registered */ |
| retain_and_null_ptr(sid); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(pci_ide_stream_register); |
| |
| /** |
| * pci_ide_stream_unregister() - unwind pci_ide_stream_register() |
| * @ide: idle IDE settings descriptor |
| * |
| * In preparation for freeing @ide, remove sysfs enumeration for the |
| * stream. |
| */ |
| void pci_ide_stream_unregister(struct pci_ide *ide) |
| { |
| struct pci_dev *pdev = ide->pdev; |
| struct pci_host_bridge *hb = pci_find_host_bridge(pdev->bus); |
| |
| sysfs_remove_link(&hb->dev.kobj, ide->name); |
| kfree(ide->name); |
| ida_free(&hb->ide_stream_ids_ida, ide->stream_id); |
| ide->name = NULL; |
| } |
| EXPORT_SYMBOL_GPL(pci_ide_stream_unregister); |
| |
| static int pci_ide_domain(struct pci_dev *pdev) |
| { |
| if (pdev->fm_enabled) |
| return pci_domain_nr(pdev->bus); |
| return 0; |
| } |
| |
| struct pci_ide_partner *pci_ide_to_settings(struct pci_dev *pdev, struct pci_ide *ide) |
| { |
| if (!pci_is_pcie(pdev)) { |
| pci_warn_once(pdev, "not a PCIe device\n"); |
| return NULL; |
| } |
| |
| switch (pci_pcie_type(pdev)) { |
| case PCI_EXP_TYPE_ENDPOINT: |
| if (pdev != ide->pdev) { |
| pci_warn_once(pdev, "setup expected Endpoint: %s\n", pci_name(ide->pdev)); |
| return NULL; |
| } |
| return &ide->partner[PCI_IDE_EP]; |
| case PCI_EXP_TYPE_ROOT_PORT: { |
| struct pci_dev *rp = pcie_find_root_port(ide->pdev); |
| |
| if (pdev != rp) { |
| pci_warn_once(pdev, "setup expected Root Port: %s\n", |
| pci_name(rp)); |
| return NULL; |
| } |
| return &ide->partner[PCI_IDE_RP]; |
| } |
| default: |
| pci_warn_once(pdev, "invalid device type\n"); |
| return NULL; |
| } |
| } |
| EXPORT_SYMBOL_GPL(pci_ide_to_settings); |
| |
| static void set_ide_sel_ctl(struct pci_dev *pdev, struct pci_ide *ide, |
| struct pci_ide_partner *settings, int pos, |
| bool enable) |
| { |
| u32 val = FIELD_PREP(PCI_IDE_SEL_CTL_ID, ide->stream_id) | |
| FIELD_PREP(PCI_IDE_SEL_CTL_DEFAULT, settings->default_stream) | |
| FIELD_PREP(PCI_IDE_SEL_CTL_CFG_EN, pdev->ide_cfg) | |
| FIELD_PREP(PCI_IDE_SEL_CTL_TEE_LIMITED, pdev->ide_tee_limit) | |
| FIELD_PREP(PCI_IDE_SEL_CTL_EN, enable); |
| |
| 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 |
| * @ide: registered IDE settings descriptor |
| * |
| * When @pdev is a PCI_EXP_TYPE_ENDPOINT then the PCI_IDE_EP partner |
| * settings are written to @pdev's Selective IDE Stream register block, |
| * and when @pdev is a PCI_EXP_TYPE_ROOT_PORT, the PCI_IDE_RP settings |
| * are selected. |
| */ |
| 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; |
| |
| if (!settings) |
| return; |
| |
| pci_ide_stream_to_regs(pdev, ide, ®s); |
| |
| pos = sel_ide_offset(pdev, settings); |
| |
| 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); |
| |
| 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); |
| } |
| |
| /* 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 |
| * stream_id is set during key programming. |
| */ |
| set_ide_sel_ctl(pdev, ide, settings, pos, false); |
| settings->setup = 1; |
| } |
| EXPORT_SYMBOL_GPL(pci_ide_stream_setup); |
| |
| /** |
| * pci_ide_stream_teardown() - disable the stream and clear all settings |
| * @pdev: PCIe device object for either a Root Port or Endpoint Partner Port |
| * @ide: registered IDE settings descriptor |
| * |
| * For stream destruction, zero all registers that may have been written |
| * by pci_ide_stream_setup(). Consider pci_ide_stream_disable() to leave |
| * settings in place while temporarily disabling the stream. |
| */ |
| 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, i; |
| |
| if (!settings) |
| return; |
| |
| 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; |
| } |
| EXPORT_SYMBOL_GPL(pci_ide_stream_teardown); |
| |
| /** |
| * pci_ide_stream_enable() - enable a Selective IDE Stream |
| * @pdev: PCIe device object for either a Root Port or Endpoint Partner Port |
| * @ide: registered and setup IDE settings descriptor |
| * |
| * Activate the stream by writing to the Selective IDE Stream Control |
| * Register. |
| * |
| * Return: 0 if the stream successfully entered the "secure" state, and -EINVAL |
| * if @ide is invalid, and -ENXIO if the stream fails to enter the secure state. |
| * |
| * Note that the state may go "insecure" at any point after returning 0, but |
| * those events are equivalent to a "link down" event and handled via |
| * asynchronous error reporting. |
| * |
| * Caller is responsible to clear the enable bit in the -ENXIO case. |
| */ |
| int pci_ide_stream_enable(struct pci_dev *pdev, struct pci_ide *ide) |
| { |
| struct pci_ide_partner *settings = pci_ide_to_settings(pdev, ide); |
| int pos; |
| u32 val; |
| |
| if (!settings) |
| return -EINVAL; |
| |
| pos = sel_ide_offset(pdev, settings); |
| |
| set_ide_sel_ctl(pdev, ide, settings, pos, true); |
| settings->enable = 1; |
| |
| pci_read_config_dword(pdev, pos + PCI_IDE_SEL_STS, &val); |
| if (FIELD_GET(PCI_IDE_SEL_STS_STATE, val) != |
| PCI_IDE_SEL_STS_STATE_SECURE) |
| return -ENXIO; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(pci_ide_stream_enable); |
| |
| /** |
| * pci_ide_stream_disable() - disable a Selective IDE Stream |
| * @pdev: PCIe device object for either a Root Port or Endpoint Partner Port |
| * @ide: registered and setup IDE settings descriptor |
| * |
| * Clear the Selective IDE Stream Control Register, but leave all other |
| * registers untouched. |
| */ |
| void pci_ide_stream_disable(struct pci_dev *pdev, struct pci_ide *ide) |
| { |
| struct pci_ide_partner *settings = pci_ide_to_settings(pdev, ide); |
| int pos; |
| |
| if (!settings) |
| return; |
| |
| pos = sel_ide_offset(pdev, settings); |
| |
| pci_write_config_dword(pdev, pos + PCI_IDE_SEL_CTL, 0); |
| settings->enable = 0; |
| } |
| EXPORT_SYMBOL_GPL(pci_ide_stream_disable); |
| |
| void pci_ide_init_host_bridge(struct pci_host_bridge *hb) |
| { |
| hb->nr_ide_streams = 256; |
| ida_init(&hb->ide_stream_ida); |
| ida_init(&hb->ide_stream_ids_ida); |
| reserve_stream_id(hb, PCI_IDE_RESERVED_STREAM_ID); |
| } |
| |
| static ssize_t available_secure_streams_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct pci_host_bridge *hb = to_pci_host_bridge(dev); |
| int nr = READ_ONCE(hb->nr_ide_streams); |
| int avail = nr; |
| |
| if (!nr) |
| return -ENXIO; |
| |
| /* |
| * Yes, this is inefficient and racy, but it is only for occasional |
| * platform resource surveys. Worst case is bounded to 256 streams. |
| */ |
| for (int i = 0; i < nr; i++) |
| if (ida_exists(&hb->ide_stream_ida, i)) |
| avail--; |
| return sysfs_emit(buf, "%d\n", avail); |
| } |
| static DEVICE_ATTR_RO(available_secure_streams); |
| |
| static struct attribute *pci_ide_attrs[] = { |
| &dev_attr_available_secure_streams.attr, |
| NULL |
| }; |
| |
| static umode_t pci_ide_attr_visible(struct kobject *kobj, struct attribute *a, int n) |
| { |
| struct device *dev = kobj_to_dev(kobj); |
| struct pci_host_bridge *hb = to_pci_host_bridge(dev); |
| |
| if (a == &dev_attr_available_secure_streams.attr) |
| if (!hb->nr_ide_streams) |
| return 0; |
| |
| return a->mode; |
| } |
| |
| const struct attribute_group pci_ide_attr_group = { |
| .attrs = pci_ide_attrs, |
| .is_visible = pci_ide_attr_visible, |
| }; |
| |
| /** |
| * pci_ide_set_nr_streams() - sets size of the pool of IDE Stream resources |
| * @hb: host bridge boundary for the stream pool |
| * @nr: number of streams |
| * |
| * Platform PCI init and/or expert test module use only. Limit IDE |
| * Stream establishment by setting the number of stream resources |
| * available at the host bridge. Platform init code must set this before |
| * the first pci_ide_stream_alloc() call if the platform has less than the |
| * default of 256 streams per host-bridge. |
| * |
| * The "PCI_IDE" symbol namespace is required because this is typically |
| * a detail that is settled in early PCI init. I.e. this export is not |
| * for endpoint drivers. |
| */ |
| void pci_ide_set_nr_streams(struct pci_host_bridge *hb, u16 nr) |
| { |
| hb->nr_ide_streams = min(nr, 256); |
| WARN_ON_ONCE(!ida_is_empty(&hb->ide_stream_ida)); |
| sysfs_update_group(&hb->dev.kobj, &pci_ide_attr_group); |
| } |
| EXPORT_SYMBOL_NS_GPL(pci_ide_set_nr_streams, "PCI_IDE"); |
| |
| void pci_ide_destroy(struct pci_dev *pdev) |
| { |
| ida_destroy(&pdev->ide_stream_ida); |
| } |