| // 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 "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_CTL, &val); | |
| if (val & PCI_IDE_SEL_CTL_EN) | |
| continue; | |
| FIELD_MODIFY(PCI_IDE_SEL_CTL_ID, &val, 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; | |
| FIELD_MODIFY(PCI_IDE_LINK_CTL_ID, &val, 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_obj(*ide); | |
| 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 - 1), | |
| pci_iov_virtfn_devfn(pdev, num_vf - 1)); | |
| 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->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); | |
| } |