diff options
Diffstat (limited to 'drivers/pci')
-rw-r--r-- | drivers/pci/Kconfig | 1 | ||||
-rw-r--r-- | drivers/pci/controller/Kconfig | 7 | ||||
-rw-r--r-- | drivers/pci/controller/Makefile | 1 | ||||
-rw-r--r-- | drivers/pci/controller/pci-hyperv-intf.c | 67 | ||||
-rw-r--r-- | drivers/pci/controller/pci-hyperv.c | 318 | ||||
-rw-r--r-- | drivers/pci/hotplug/Kconfig | 9 | ||||
-rw-r--r-- | drivers/pci/hotplug/Makefile | 1 | ||||
-rw-r--r-- | drivers/pci/hotplug/pnv_php.c | 59 | ||||
-rw-r--r-- | drivers/pci/hotplug/rpaphp_core.c | 18 | ||||
-rw-r--r-- | drivers/pci/hotplug/sgi_hotplug.c | 700 | ||||
-rw-r--r-- | drivers/pci/pci.c | 29 | ||||
-rw-r--r-- | drivers/pci/pci.h | 1 | ||||
-rw-r--r-- | drivers/pci/pcie/aspm.c | 20 | ||||
-rw-r--r-- | drivers/pci/pcie/portdrv_core.c | 66 | ||||
-rw-r--r-- | drivers/pci/probe.c | 7 | ||||
-rw-r--r-- | drivers/pci/quirks.c | 2 |
16 files changed, 490 insertions, 816 deletions
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index a5017b66bd9d..a304f5ea11b9 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -182,6 +182,7 @@ config PCI_LABEL config PCI_HYPERV tristate "Hyper-V PCI Frontend" depends on X86_64 && HYPERV && PCI_MSI && PCI_MSI_IRQ_DOMAIN && SYSFS + select PCI_HYPERV_INTERFACE help The PCI device frontend driver allows the kernel to import arbitrary PCI devices from a PCI backend to support PCI driver domains. diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig index fe9f9f13ce11..70e078238899 100644 --- a/drivers/pci/controller/Kconfig +++ b/drivers/pci/controller/Kconfig @@ -281,5 +281,12 @@ config VMD To compile this driver as a module, choose M here: the module will be called vmd. +config PCI_HYPERV_INTERFACE + tristate "Hyper-V PCI Interface" + depends on X86 && HYPERV && PCI_MSI && PCI_MSI_IRQ_DOMAIN && X86_64 + help + The Hyper-V PCI Interface is a helper driver allows other drivers to + have a common interface with the Hyper-V PCI frontend driver. + source "drivers/pci/controller/dwc/Kconfig" endmenu diff --git a/drivers/pci/controller/Makefile b/drivers/pci/controller/Makefile index d56a507495c5..a2a22c9d91af 100644 --- a/drivers/pci/controller/Makefile +++ b/drivers/pci/controller/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_PCIE_CADENCE_HOST) += pcie-cadence-host.o obj-$(CONFIG_PCIE_CADENCE_EP) += pcie-cadence-ep.o obj-$(CONFIG_PCI_FTPCI100) += pci-ftpci100.o obj-$(CONFIG_PCI_HYPERV) += pci-hyperv.o +obj-$(CONFIG_PCI_HYPERV_INTERFACE) += pci-hyperv-intf.o obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o obj-$(CONFIG_PCI_AARDVARK) += pci-aardvark.o obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o diff --git a/drivers/pci/controller/pci-hyperv-intf.c b/drivers/pci/controller/pci-hyperv-intf.c new file mode 100644 index 000000000000..cc96be450360 --- /dev/null +++ b/drivers/pci/controller/pci-hyperv-intf.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) Microsoft Corporation. + * + * Author: + * Haiyang Zhang <haiyangz@microsoft.com> + * + * This small module is a helper driver allows other drivers to + * have a common interface with the Hyper-V PCI frontend driver. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/hyperv.h> + +struct hyperv_pci_block_ops hvpci_block_ops; +EXPORT_SYMBOL_GPL(hvpci_block_ops); + +int hyperv_read_cfg_blk(struct pci_dev *dev, void *buf, unsigned int buf_len, + unsigned int block_id, unsigned int *bytes_returned) +{ + if (!hvpci_block_ops.read_block) + return -EOPNOTSUPP; + + return hvpci_block_ops.read_block(dev, buf, buf_len, block_id, + bytes_returned); +} +EXPORT_SYMBOL_GPL(hyperv_read_cfg_blk); + +int hyperv_write_cfg_blk(struct pci_dev *dev, void *buf, unsigned int len, + unsigned int block_id) +{ + if (!hvpci_block_ops.write_block) + return -EOPNOTSUPP; + + return hvpci_block_ops.write_block(dev, buf, len, block_id); +} +EXPORT_SYMBOL_GPL(hyperv_write_cfg_blk); + +int hyperv_reg_block_invalidate(struct pci_dev *dev, void *context, + void (*block_invalidate)(void *context, + u64 block_mask)) +{ + if (!hvpci_block_ops.reg_blk_invalidate) + return -EOPNOTSUPP; + + return hvpci_block_ops.reg_blk_invalidate(dev, context, + block_invalidate); +} +EXPORT_SYMBOL_GPL(hyperv_reg_block_invalidate); + +static void __exit exit_hv_pci_intf(void) +{ +} + +static int __init init_hv_pci_intf(void) +{ + return 0; +} + +module_init(init_hv_pci_intf); +module_exit(exit_hv_pci_intf); + +MODULE_DESCRIPTION("Hyper-V PCI Interface"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/controller/pci-hyperv.c b/drivers/pci/controller/pci-hyperv.c index 3a56de6b2ec2..f1f300218fab 100644 --- a/drivers/pci/controller/pci-hyperv.c +++ b/drivers/pci/controller/pci-hyperv.c @@ -365,6 +365,39 @@ struct pci_delete_interrupt { struct tran_int_desc int_desc; } __packed; +/* + * Note: the VM must pass a valid block id, wslot and bytes_requested. + */ +struct pci_read_block { + struct pci_message message_type; + u32 block_id; + union win_slot_encoding wslot; + u32 bytes_requested; +} __packed; + +struct pci_read_block_response { + struct vmpacket_descriptor hdr; + u32 status; + u8 bytes[HV_CONFIG_BLOCK_SIZE_MAX]; +} __packed; + +/* + * Note: the VM must pass a valid block id, wslot and byte_count. + */ +struct pci_write_block { + struct pci_message message_type; + u32 block_id; + union win_slot_encoding wslot; + u32 byte_count; + u8 bytes[HV_CONFIG_BLOCK_SIZE_MAX]; +} __packed; + +struct pci_dev_inval_block { + struct pci_incoming_message incoming; + union win_slot_encoding wslot; + u64 block_mask; +} __packed; + struct pci_dev_incoming { struct pci_incoming_message incoming; union win_slot_encoding wslot; @@ -499,6 +532,9 @@ struct hv_pci_dev { struct hv_pcibus_device *hbus; struct work_struct wrk; + void (*block_invalidate)(void *context, u64 block_mask); + void *invalidate_context; + /* * What would be observed if one wrote 0xFFFFFFFF to a BAR and then * read it back, for each of the BAR offsets within config space. @@ -817,6 +853,253 @@ static struct pci_ops hv_pcifront_ops = { .write = hv_pcifront_write_config, }; +/* + * Paravirtual backchannel + * + * Hyper-V SR-IOV provides a backchannel mechanism in software for + * communication between a VF driver and a PF driver. These + * "configuration blocks" are similar in concept to PCI configuration space, + * but instead of doing reads and writes in 32-bit chunks through a very slow + * path, packets of up to 128 bytes can be sent or received asynchronously. + * + * Nearly every SR-IOV device contains just such a communications channel in + * hardware, so using this one in software is usually optional. Using the + * software channel, however, allows driver implementers to leverage software + * tools that fuzz the communications channel looking for vulnerabilities. + * + * The usage model for these packets puts the responsibility for reading or + * writing on the VF driver. The VF driver sends a read or a write packet, + * indicating which "block" is being referred to by number. + * + * If the PF driver wishes to initiate communication, it can "invalidate" one or + * more of the first 64 blocks. This invalidation is delivered via a callback + * supplied by the VF driver by this driver. + * + * No protocol is implied, except that supplied by the PF and VF drivers. + */ + +struct hv_read_config_compl { + struct hv_pci_compl comp_pkt; + void *buf; + unsigned int len; + unsigned int bytes_returned; +}; + +/** + * hv_pci_read_config_compl() - Invoked when a response packet + * for a read config block operation arrives. + * @context: Identifies the read config operation + * @resp: The response packet itself + * @resp_packet_size: Size in bytes of the response packet + */ +static void hv_pci_read_config_compl(void *context, struct pci_response *resp, + int resp_packet_size) +{ + struct hv_read_config_compl *comp = context; + struct pci_read_block_response *read_resp = + (struct pci_read_block_response *)resp; + unsigned int data_len, hdr_len; + + hdr_len = offsetof(struct pci_read_block_response, bytes); + if (resp_packet_size < hdr_len) { + comp->comp_pkt.completion_status = -1; + goto out; + } + + data_len = resp_packet_size - hdr_len; + if (data_len > 0 && read_resp->status == 0) { + comp->bytes_returned = min(comp->len, data_len); + memcpy(comp->buf, read_resp->bytes, comp->bytes_returned); + } else { + comp->bytes_returned = 0; + } + + comp->comp_pkt.completion_status = read_resp->status; +out: + complete(&comp->comp_pkt.host_event); +} + +/** + * hv_read_config_block() - Sends a read config block request to + * the back-end driver running in the Hyper-V parent partition. + * @pdev: The PCI driver's representation for this device. + * @buf: Buffer into which the config block will be copied. + * @len: Size in bytes of buf. + * @block_id: Identifies the config block which has been requested. + * @bytes_returned: Size which came back from the back-end driver. + * + * Return: 0 on success, -errno on failure + */ +int hv_read_config_block(struct pci_dev *pdev, void *buf, unsigned int len, + unsigned int block_id, unsigned int *bytes_returned) +{ + struct hv_pcibus_device *hbus = + container_of(pdev->bus->sysdata, struct hv_pcibus_device, + sysdata); + struct { + struct pci_packet pkt; + char buf[sizeof(struct pci_read_block)]; + } pkt; + struct hv_read_config_compl comp_pkt; + struct pci_read_block *read_blk; + int ret; + + if (len == 0 || len > HV_CONFIG_BLOCK_SIZE_MAX) + return -EINVAL; + + init_completion(&comp_pkt.comp_pkt.host_event); + comp_pkt.buf = buf; + comp_pkt.len = len; + + memset(&pkt, 0, sizeof(pkt)); + pkt.pkt.completion_func = hv_pci_read_config_compl; + pkt.pkt.compl_ctxt = &comp_pkt; + read_blk = (struct pci_read_block *)&pkt.pkt.message; + read_blk->message_type.type = PCI_READ_BLOCK; + read_blk->wslot.slot = devfn_to_wslot(pdev->devfn); + read_blk->block_id = block_id; + read_blk->bytes_requested = len; + + ret = vmbus_sendpacket(hbus->hdev->channel, read_blk, + sizeof(*read_blk), (unsigned long)&pkt.pkt, + VM_PKT_DATA_INBAND, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (ret) + return ret; + + ret = wait_for_response(hbus->hdev, &comp_pkt.comp_pkt.host_event); + if (ret) + return ret; + + if (comp_pkt.comp_pkt.completion_status != 0 || + comp_pkt.bytes_returned == 0) { + dev_err(&hbus->hdev->device, + "Read Config Block failed: 0x%x, bytes_returned=%d\n", + comp_pkt.comp_pkt.completion_status, + comp_pkt.bytes_returned); + return -EIO; + } + + *bytes_returned = comp_pkt.bytes_returned; + return 0; +} + +/** + * hv_pci_write_config_compl() - Invoked when a response packet for a write + * config block operation arrives. + * @context: Identifies the write config operation + * @resp: The response packet itself + * @resp_packet_size: Size in bytes of the response packet + */ +static void hv_pci_write_config_compl(void *context, struct pci_response *resp, + int resp_packet_size) +{ + struct hv_pci_compl *comp_pkt = context; + + comp_pkt->completion_status = resp->status; + complete(&comp_pkt->host_event); +} + +/** + * hv_write_config_block() - Sends a write config block request to the + * back-end driver running in the Hyper-V parent partition. + * @pdev: The PCI driver's representation for this device. + * @buf: Buffer from which the config block will be copied. + * @len: Size in bytes of buf. + * @block_id: Identifies the config block which is being written. + * + * Return: 0 on success, -errno on failure + */ +int hv_write_config_block(struct pci_dev *pdev, void *buf, unsigned int len, + unsigned int block_id) +{ + struct hv_pcibus_device *hbus = + container_of(pdev->bus->sysdata, struct hv_pcibus_device, + sysdata); + struct { + struct pci_packet pkt; + char buf[sizeof(struct pci_write_block)]; + u32 reserved; + } pkt; + struct hv_pci_compl comp_pkt; + struct pci_write_block *write_blk; + u32 pkt_size; + int ret; + + if (len == 0 || len > HV_CONFIG_BLOCK_SIZE_MAX) + return -EINVAL; + + init_completion(&comp_pkt.host_event); + + memset(&pkt, 0, sizeof(pkt)); + pkt.pkt.completion_func = hv_pci_write_config_compl; + pkt.pkt.compl_ctxt = &comp_pkt; + write_blk = (struct pci_write_block *)&pkt.pkt.message; + write_blk->message_type.type = PCI_WRITE_BLOCK; + write_blk->wslot.slot = devfn_to_wslot(pdev->devfn); + write_blk->block_id = block_id; + write_blk->byte_count = len; + memcpy(write_blk->bytes, buf, len); + pkt_size = offsetof(struct pci_write_block, bytes) + len; + /* + * This quirk is required on some hosts shipped around 2018, because + * these hosts don't check the pkt_size correctly (new hosts have been + * fixed since early 2019). The quirk is also safe on very old hosts + * and new hosts, because, on them, what really matters is the length + * specified in write_blk->byte_count. + */ + pkt_size += sizeof(pkt.reserved); + + ret = vmbus_sendpacket(hbus->hdev->channel, write_blk, pkt_size, + (unsigned long)&pkt.pkt, VM_PKT_DATA_INBAND, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (ret) + return ret; + + ret = wait_for_response(hbus->hdev, &comp_pkt.host_event); + if (ret) + return ret; + + if (comp_pkt.completion_status != 0) { + dev_err(&hbus->hdev->device, + "Write Config Block failed: 0x%x\n", + comp_pkt.completion_status); + return -EIO; + } + + return 0; +} + +/** + * hv_register_block_invalidate() - Invoked when a config block invalidation + * arrives from the back-end driver. + * @pdev: The PCI driver's representation for this device. + * @context: Identifies the device. + * @block_invalidate: Identifies all of the blocks being invalidated. + * + * Return: 0 on success, -errno on failure + */ +int hv_register_block_invalidate(struct pci_dev *pdev, void *context, + void (*block_invalidate)(void *context, + u64 block_mask)) +{ + struct hv_pcibus_device *hbus = + container_of(pdev->bus->sysdata, struct hv_pcibus_device, + sysdata); + struct hv_pci_dev *hpdev; + + hpdev = get_pcichild_wslot(hbus, devfn_to_wslot(pdev->devfn)); + if (!hpdev) + return -ENODEV; + + hpdev->block_invalidate = block_invalidate; + hpdev->invalidate_context = context; + + put_pcichild(hpdev); + return 0; + +} + /* Interrupt management hooks */ static void hv_int_desc_free(struct hv_pci_dev *hpdev, struct tran_int_desc *int_desc) @@ -1968,6 +2251,7 @@ static void hv_pci_onchannelcallback(void *context) struct pci_response *response; struct pci_incoming_message *new_message; struct pci_bus_relations *bus_rel; + struct pci_dev_inval_block *inval; struct pci_dev_incoming *dev_message; struct hv_pci_dev *hpdev; @@ -2045,6 +2329,21 @@ static void hv_pci_onchannelcallback(void *context) } break; + case PCI_INVALIDATE_BLOCK: + + inval = (struct pci_dev_inval_block *)buffer; + hpdev = get_pcichild_wslot(hbus, + inval->wslot.slot); + if (hpdev) { + if (hpdev->block_invalidate) { + hpdev->block_invalidate( + hpdev->invalidate_context, + inval->block_mask); + } + put_pcichild(hpdev); + } + break; + default: dev_warn(&hbus->hdev->device, "Unimplemented protocol message %x\n", @@ -2564,6 +2863,7 @@ static int hv_pci_probe(struct hv_device *hdev, { struct hv_pcibus_device *hbus; u16 dom_req, dom; + char *name; int ret; /* @@ -2647,7 +2947,14 @@ static int hv_pci_probe(struct hv_device *hdev, goto free_config; } - hbus->sysdata.fwnode = irq_domain_alloc_fwnode(hbus); + name = kasprintf(GFP_KERNEL, "%pUL", &hdev->dev_instance); + if (!name) { + ret = -ENOMEM; + goto unmap; + } + + hbus->sysdata.fwnode = irq_domain_alloc_named_fwnode(name); + kfree(name); if (!hbus->sysdata.fwnode) { ret = -ENOMEM; goto unmap; @@ -2806,6 +3113,10 @@ static struct hv_driver hv_pci_drv = { static void __exit exit_hv_pci_drv(void) { vmbus_driver_unregister(&hv_pci_drv); + + hvpci_block_ops.read_block = NULL; + hvpci_block_ops.write_block = NULL; + hvpci_block_ops.reg_blk_invalidate = NULL; } static int __init init_hv_pci_drv(void) @@ -2813,6 +3124,11 @@ static int __init init_hv_pci_drv(void) /* Set the invalid domain number's bit, so it will not be used */ set_bit(HVPCI_DOM_INVALID, hvpci_dom_map); + /* Initialize PCI block r/w interface */ + hvpci_block_ops.read_block = hv_read_config_block; + hvpci_block_ops.write_block = hv_write_config_block; + hvpci_block_ops.reg_blk_invalidate = hv_register_block_invalidate; + return vmbus_driver_register(&hv_pci_drv); } diff --git a/drivers/pci/hotplug/Kconfig b/drivers/pci/hotplug/Kconfig index e9f78eb390d2..e7b493c22bf3 100644 --- a/drivers/pci/hotplug/Kconfig +++ b/drivers/pci/hotplug/Kconfig @@ -147,15 +147,6 @@ config HOTPLUG_PCI_RPA_DLPAR When in doubt, say N. -config HOTPLUG_PCI_SGI - tristate "SGI PCI Hotplug Support" - depends on IA64_SGI_SN2 || IA64_GENERIC - help - Say Y here if you want to use the SGI Altix Hotplug - Driver for PCI devices. - - When in doubt, say N. - config HOTPLUG_PCI_S390 bool "System z PCI Hotplug Support" depends on S390 && 64BIT diff --git a/drivers/pci/hotplug/Makefile b/drivers/pci/hotplug/Makefile index 7e3331603714..5196983220df 100644 --- a/drivers/pci/hotplug/Makefile +++ b/drivers/pci/hotplug/Makefile @@ -18,7 +18,6 @@ obj-$(CONFIG_HOTPLUG_PCI_SHPC) += shpchp.o obj-$(CONFIG_HOTPLUG_PCI_POWERNV) += pnv-php.o obj-$(CONFIG_HOTPLUG_PCI_RPA) += rpaphp.o obj-$(CONFIG_HOTPLUG_PCI_RPA_DLPAR) += rpadlpar_io.o -obj-$(CONFIG_HOTPLUG_PCI_SGI) += sgi_hotplug.o obj-$(CONFIG_HOTPLUG_PCI_ACPI) += acpiphp.o obj-$(CONFIG_HOTPLUG_PCI_S390) += s390_pci_hpc.o diff --git a/drivers/pci/hotplug/pnv_php.c b/drivers/pci/hotplug/pnv_php.c index 6758fd7c382e..d7b2b47bc33e 100644 --- a/drivers/pci/hotplug/pnv_php.c +++ b/drivers/pci/hotplug/pnv_php.c @@ -419,9 +419,21 @@ static int pnv_php_get_attention_state(struct hotplug_slot *slot, u8 *state) static int pnv_php_set_attention_state(struct hotplug_slot *slot, u8 state) { struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); + struct pci_dev *bridge = php_slot->pdev; + u16 new, mask; - /* FIXME: Make it real once firmware supports it */ php_slot->attention_state = state; + if (!bridge) + return 0; + + mask = PCI_EXP_SLTCTL_AIC; + + if (state) + new = PCI_EXP_SLTCTL_ATTN_IND_ON; + else + new = PCI_EXP_SLTCTL_ATTN_IND_OFF; + + pcie_capability_clear_and_set_word(bridge, PCI_EXP_SLTCTL, mask, new); return 0; } @@ -511,6 +523,37 @@ scan: return 0; } +static int pnv_php_reset_slot(struct hotplug_slot *slot, int probe) +{ + struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); + struct pci_dev *bridge = php_slot->pdev; + uint16_t sts; + + /* + * The CAPI folks want pnv_php to drive OpenCAPI slots + * which don't have a bridge. Only claim to support + * reset_slot() if we have a bridge device (for now...) + */ + if (probe) + return !bridge; + + /* mask our interrupt while resetting the bridge */ + if (php_slot->irq > 0) + disable_irq(php_slot->irq); + + pci_bridge_secondary_bus_reset(bridge); + + /* clear any state changes that happened due to the reset */ + pcie_capability_read_word(php_slot->pdev, PCI_EXP_SLTSTA, &sts); + sts &= (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC); + pcie_capability_write_word(php_slot->pdev, PCI_EXP_SLTSTA, sts); + + if (php_slot->irq > 0) + enable_irq(php_slot->irq); + + return 0; +} + static int pnv_php_enable_slot(struct hotplug_slot *slot) { struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); @@ -548,6 +591,7 @@ static const struct hotplug_slot_ops php_slot_ops = { .set_attention_status = pnv_php_set_attention_state, .enable_slot = pnv_php_enable_slot, .disable_slot = pnv_php_disable_slot, + .reset_slot = pnv_php_reset_slot, }; static void pnv_php_release(struct pnv_php_slot *php_slot) @@ -721,6 +765,12 @@ static irqreturn_t pnv_php_interrupt(int irq, void *data) pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &sts); sts &= (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC); pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, sts); + + pci_dbg(pdev, "PCI slot [%s]: HP int! DLAct: %d, PresDet: %d\n", + php_slot->name, + !!(sts & PCI_EXP_SLTSTA_DLLSC), + !!(sts & PCI_EXP_SLTSTA_PDC)); + if (sts & PCI_EXP_SLTSTA_DLLSC) { pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lsts); added = !!(lsts & PCI_EXP_LNKSTA_DLLLA); @@ -735,6 +785,7 @@ static irqreturn_t pnv_php_interrupt(int irq, void *data) added = !!(presence == OPAL_PCI_SLOT_PRESENT); } else { + pci_dbg(pdev, "PCI slot [%s]: Spurious IRQ?\n", php_slot->name); return IRQ_NONE; } @@ -955,6 +1006,9 @@ static int __init pnv_php_init(void) for_each_compatible_node(dn, NULL, "ibm,ioda2-phb") pnv_php_register(dn); + for_each_compatible_node(dn, NULL, "ibm,ioda3-phb") + pnv_php_register(dn); + return 0; } @@ -964,6 +1018,9 @@ static void __exit pnv_php_exit(void) for_each_compatible_node(dn, NULL, "ibm,ioda2-phb") pnv_php_unregister(dn); + + for_each_compatible_node(dn, NULL, "ibm,ioda3-phb") + pnv_php_unregister(dn); } module_init(pnv_php_init); diff --git a/drivers/pci/hotplug/rpaphp_core.c b/drivers/pci/hotplug/rpaphp_core.c index d34a3baf9b3a..18627bb21e9e 100644 --- a/drivers/pci/hotplug/rpaphp_core.c +++ b/drivers/pci/hotplug/rpaphp_core.c @@ -230,7 +230,7 @@ static int rpaphp_check_drc_props_v2(struct device_node *dn, char *drc_name, struct of_drc_info drc; const __be32 *value; char cell_drc_name[MAX_DRC_NAME_LEN]; - int j, fndit; + int j; info = of_find_property(dn->parent, "ibm,drc-info", NULL); if (info == NULL) @@ -245,17 +245,13 @@ static int rpaphp_check_drc_props_v2(struct device_node *dn, char *drc_name, /* Should now know end of current entry */ - if (my_index > drc.last_drc_index) - continue; - - fndit = 1; - break; + /* Found it */ + if (my_index <= drc.last_drc_index) { + sprintf(cell_drc_name, "%s%d", drc.drc_name_prefix, + my_index); + break; + } } - /* Found it */ - - if (fndit) - sprintf(cell_drc_name, "%s%d", drc.drc_name_prefix, - my_index); if (((drc_name == NULL) || (drc_name && !strcmp(drc_name, cell_drc_name))) && diff --git a/drivers/pci/hotplug/sgi_hotplug.c b/drivers/pci/hotplug/sgi_hotplug.c deleted file mode 100644 index 231f5bdd3d2d..000000000000 --- a/drivers/pci/hotplug/sgi_hotplug.c +++ /dev/null @@ -1,700 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (C) 2005-2006 Silicon Graphics, Inc. All rights reserved. - * - * This work was based on the 2.4/2.6 kernel development by Dick Reigner. - * Work to add BIOS PROM support was completed by Mike Habeck. - */ - -#include <linux/acpi.h> -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/pci.h> -#include <linux/pci_hotplug.h> -#include <linux/proc_fs.h> -#include <linux/slab.h> -#include <linux/types.h> -#include <linux/mutex.h> - -#include <asm/sn/addrs.h> -#include <asm/sn/geo.h> -#include <asm/sn/l1.h> -#include <asm/sn/module.h> -#include <asm/sn/pcibr_provider.h> -#include <asm/sn/pcibus_provider_defs.h> -#include <asm/sn/pcidev.h> -#include <asm/sn/sn_feature_sets.h> -#include <asm/sn/sn_sal.h> -#include <asm/sn/types.h> -#include <asm/sn/acpi.h> - -#include "../pci.h" - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("SGI (prarit@sgi.com, dickie@sgi.com, habeck@sgi.com)"); -MODULE_DESCRIPTION("SGI Altix Hot Plug PCI Controller Driver"); - - -/* SAL call error codes. Keep in sync with prom header io/include/pcibr.h */ -#define PCI_SLOT_ALREADY_UP 2 /* slot already up */ -#define PCI_SLOT_ALREADY_DOWN 3 /* slot already down */ -#define PCI_L1_ERR 7 /* L1 console command error */ -#define PCI_EMPTY_33MHZ 15 /* empty 33 MHz bus */ - - -#define PCIIO_ASIC_TYPE_TIOCA 4 -#define PCI_L1_QSIZE 128 /* our L1 message buffer size */ -#define SN_MAX_HP_SLOTS 32 /* max hotplug slots */ -#define SN_SLOT_NAME_SIZE 33 /* size of name string */ - -/* internal list head */ -static struct list_head sn_hp_list; - -/* hotplug_slot struct's private pointer */ -struct slot { - int device_num; - struct pci_bus *pci_bus; - /* this struct for glue internal only */ - struct hotplug_slot hotplug_slot; - struct list_head hp_list; - char physical_path[SN_SLOT_NAME_SIZE]; -}; - -struct pcibr_slot_enable_resp { - int resp_sub_errno; - char resp_l1_msg[PCI_L1_QSIZE + 1]; -}; - -struct pcibr_slot_disable_resp { - int resp_sub_errno; - char resp_l1_msg[PCI_L1_QSIZE + 1]; -}; - -enum sn_pci_req_e { - PCI_REQ_SLOT_ELIGIBLE, - PCI_REQ_SLOT_DISABLE -}; - -static int enable_slot(struct hotplug_slot *slot); -static int disable_slot(struct hotplug_slot *slot); -static inline int get_power_status(struct hotplug_slot *slot, u8 *value); - -static const struct hotplug_slot_ops sn_hotplug_slot_ops = { - .enable_slot = enable_slot, - .disable_slot = disable_slot, - .get_power_status = get_power_status, -}; - -static DEFINE_MUTEX(sn_hotplug_mutex); - -static struct slot *to_slot(struct hotplug_slot *bss_hotplug_slot) -{ - return container_of(bss_hotplug_slot, struct slot, hotplug_slot); -} - -static ssize_t path_show(struct pci_slot *pci_slot, char *buf) -{ - int retval = -ENOENT; - struct slot *slot = to_slot(pci_slot->hotplug); - - if (!slot) - return retval; - - retval = sprintf(buf, "%s\n", slot->physical_path); - return retval; -} - -static struct pci_slot_attribute sn_slot_path_attr = __ATTR_RO(path); - -static int sn_pci_slot_valid(struct pci_bus *pci_bus, int device) -{ - struct pcibus_info *pcibus_info; - u16 busnum, segment, ioboard_type; - - pcibus_info = SN_PCIBUS_BUSSOFT_INFO(pci_bus); - - /* Check to see if this is a valid slot on 'pci_bus' */ - if (!(pcibus_info->pbi_valid_devices & (1 << device))) - return -EPERM; - - ioboard_type = sn_ioboard_to_pci_bus(pci_bus); - busnum = pcibus_info->pbi_buscommon.bs_persist_busnum; - segment = pci_domain_nr(pci_bus) & 0xf; - - /* Do not allow hotplug operations on base I/O cards */ - if ((ioboard_type == L1_BRICKTYPE_IX || - ioboard_type == L1_BRICKTYPE_IA) && - (segment == 1 && busnum == 0 && device != 1)) - return -EPERM; - - return 1; -} - -static int sn_pci_bus_valid(struct pci_bus *pci_bus) -{ - struct pcibus_info *pcibus_info; - u32 asic_type; - u16 ioboard_type; - - /* Don't register slots hanging off the TIOCA bus */ - pcibus_info = SN_PCIBUS_BUSSOFT_INFO(pci_bus); - asic_type = pcibus_info->pbi_buscommon.bs_asic_type; - if (asic_type == PCIIO_ASIC_TYPE_TIOCA) - return -EPERM; - - /* Only register slots in I/O Bricks that support hotplug */ - ioboard_type = sn_ioboard_to_pci_bus(pci_bus); - switch (ioboard_type) { - case L1_BRICKTYPE_IX: - case L1_BRICKTYPE_PX: - case L1_BRICKTYPE_IA: - case L1_BRICKTYPE_PA: - case L1_BOARDTYPE_PCIX3SLOT: - return 1; - break; - default: - return -EPERM; - break; - } - - return -EIO; -} - -static int sn_hp_slot_private_alloc(struct hotplug_slot **bss_hotplug_slot, - struct pci_bus *pci_bus, int device, - char *name) -{ - struct pcibus_info *pcibus_info; - struct slot *slot; - - pcibus_info = SN_PCIBUS_BUSSOFT_INFO(pci_bus); - - slot = kzalloc(sizeof(*slot), GFP_KERNEL); - if (!slot) - return -ENOMEM; - - slot->device_num = device; - slot->pci_bus = pci_bus; - sprintf(name, "%04x:%02x:%02x", - pci_domain_nr(pci_bus), - ((u16)pcibus_info->pbi_buscommon.bs_persist_busnum), - device + 1); - - sn_generate_path(pci_bus, slot->physical_path); - - list_add(&slot->hp_list, &sn_hp_list); - *bss_hotplug_slot = &slot->hotplug_slot; - - return 0; -} - -static struct hotplug_slot *sn_hp_destroy(void) -{ - struct slot *slot; - struct pci_slot *pci_slot; - struct hotplug_slot *bss_hotplug_slot = NULL; - - list_for_each_entry(slot, &sn_hp_list, hp_list) { - bss_hotplug_slot = &slot->hotplug_slot; - pci_slot = bss_hotplug_slot->pci_slot; - list_del(&slot->hp_list); - sysfs_remove_file(&pci_slot->kobj, - &sn_slot_path_attr.attr); - break; - } - return bss_hotplug_slot; -} - -static void sn_bus_free_data(struct pci_dev *dev) -{ - struct pci_bus *subordinate_bus; - struct pci_dev *child; - - /* Recursively clean up sn_irq_info structs */ - if (dev->subordinate) { - subordinate_bus = dev->subordinate; - list_for_each_entry(child, &subordinate_bus->devices, bus_list) - sn_bus_free_data(child); - } - /* - * Some drivers may use dma accesses during the - * driver remove function. We release the sysdata - * areas after the driver remove functions have - * been called. - */ - sn_bus_store_sysdata(dev); - sn_pci_unfixup_slot(dev); -} - -static int sn_slot_enable(struct hotplug_slot *bss_hotplug_slot, - int device_num, char **ssdt) -{ - struct slot *slot = to_slot(bss_hotplug_slot); - struct pcibus_info *pcibus_info; - struct pcibr_slot_enable_resp resp; - int rc; - - pcibus_info = SN_PCIBUS_BUSSOFT_INFO(slot->pci_bus); - - /* - * Power-on and initialize the slot in the SN - * PCI infrastructure. - */ - rc = sal_pcibr_slot_enable(pcibus_info, device_num, &resp, ssdt); - - - if (rc == PCI_SLOT_ALREADY_UP) { - pci_dbg(slot->pci_bus->self, "is already active\n"); - return 1; /* return 1 to user */ - } - - if (rc == PCI_L1_ERR) { - pci_dbg(slot->pci_bus->self, "L1 failure %d with message: %s", - resp.resp_sub_errno, resp.resp_l1_msg); - return -EPERM; - } - - if (rc) { - pci_dbg(slot->pci_bus->self, "insert failed with error %d sub-error %d\n", - rc, resp.resp_sub_errno); - return -EIO; - } - - pcibus_info = SN_PCIBUS_BUSSOFT_INFO(slot->pci_bus); - pcibus_info->pbi_enabled_devices |= (1 << device_num); - - return 0; -} - -static int sn_slot_disable(struct hotplug_slot *bss_hotplug_slot, - int device_num, int action) -{ - struct slot *slot = to_slot(bss_hotplug_slot); - struct pcibus_info *pcibus_info; - struct pcibr_slot_disable_resp resp; - int rc; - - pcibus_info = SN_PCIBUS_BUSSOFT_INFO(slot->pci_bus); - - rc = sal_pcibr_slot_disable(pcibus_info, device_num, action, &resp); - - if ((action == PCI_REQ_SLOT_ELIGIBLE) && - (rc == PCI_SLOT_ALREADY_DOWN)) { - pci_dbg(slot->pci_bus->self, "Slot %s already inactive\n", slot->physical_path); - return 1; /* return 1 to user */ - } - - if ((action == PCI_REQ_SLOT_ELIGIBLE) && (rc == PCI_EMPTY_33MHZ)) { - pci_dbg(slot->pci_bus->self, "Cannot remove last 33MHz card\n"); - return -EPERM; - } - - if ((action == PCI_REQ_SLOT_ELIGIBLE) && (rc == PCI_L1_ERR)) { - pci_dbg(slot->pci_bus->self, "L1 failure %d with message \n%s\n", - resp.resp_sub_errno, resp.resp_l1_msg); - return -EPERM; - } - - if ((action == PCI_REQ_SLOT_ELIGIBLE) && rc) { - pci_dbg(slot->pci_bus->self, "remove failed with error %d sub-error %d\n", - rc, resp.resp_sub_errno); - return -EIO; - } - - if ((action == PCI_REQ_SLOT_ELIGIBLE) && !rc) - return 0; - - if ((action == PCI_REQ_SLOT_DISABLE) && !rc) { - pcibus_info = SN_PCIBUS_BUSSOFT_INFO(slot->pci_bus); - pcibus_info->pbi_enabled_devices &= ~(1 << device_num); - pci_dbg(slot->pci_bus->self, "remove successful\n"); - return 0; - } - - if ((action == PCI_REQ_SLOT_DISABLE) && rc) { - pci_dbg(slot->pci_bus->self, "remove failed rc = %d\n", rc); - } - - return rc; -} - -/* - * Power up and configure the slot via a SAL call to PROM. - * Scan slot (and any children), do any platform specific fixup, - * and find device driver. - */ -static int enable_slot(struct hotplug_slot *bss_hotplug_slot) -{ - struct slot *slot = to_slot(bss_hotplug_slot); - struct pci_bus *new_bus = NULL; - struct pci_dev *dev; - int num_funcs; - int new_ppb = 0; - int rc; - char *ssdt = NULL; - void pcibios_fixup_device_resources(struct pci_dev *); - - /* Serialize the Linux PCI infrastructure */ - mutex_lock(&sn_hotplug_mutex); - - /* - * Power-on and initialize the slot in the SN - * PCI infrastructure. Also, retrieve the ACPI SSDT - * table for the slot (if ACPI capable PROM). - */ - rc = sn_slot_enable(bss_hotplug_slot, slot->device_num, &ssdt); - if (rc) { - mutex_unlock(&sn_hotplug_mutex); - return rc; - } - - if (ssdt) - ssdt = __va(ssdt); - /* Add the new SSDT for the slot to the ACPI namespace */ - if (SN_ACPI_BASE_SUPPORT() && ssdt) { - acpi_status ret; - - ret = acpi_load_table((struct acpi_table_header *)ssdt); - if (ACPI_FAILURE(ret)) { - printk(KERN_ERR "%s: acpi_load_table failed (0x%x)\n", - __func__, ret); - /* try to continue on */ - } - } - - num_funcs = pci_scan_slot(slot->pci_bus, - PCI_DEVFN(slot->device_num + 1, 0)); - if (!num_funcs) { - pci_dbg(slot->pci_bus->self, "no device in slot\n"); - mutex_unlock(&sn_hotplug_mutex); - return -ENODEV; - } - - /* - * Map SN resources for all functions on the card - * to the Linux PCI interface and tell the drivers - * about them. - */ - list_for_each_entry(dev, &slot->pci_bus->devices, bus_list) { - if (PCI_SLOT(dev->devfn) != slot->device_num + 1) - continue; - - /* Need to do slot fixup on PPB before fixup of children - * (PPB's pcidev_info needs to be in pcidev_info list - * before child's SN_PCIDEV_INFO() call to setup - * pdi_host_pcidev_info). - */ - pcibios_fixup_device_resources(dev); - if (SN_ACPI_BASE_SUPPORT()) - sn_acpi_slot_fixup(dev); - else - sn_io_slot_fixup(dev); - if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { - pci_hp_add_bridge(dev); - if (dev->subordinate) { - new_bus = dev->subordinate; - new_ppb = 1; - } - } - } - - /* - * Add the slot's devices to the ACPI infrastructure */ - if (SN_ACPI_BASE_SUPPORT() && ssdt) { - unsigned long long adr; - struct acpi_device *pdevice; - acpi_handle phandle; - acpi_handle chandle = NULL; - acpi_handle rethandle; - acpi_status ret; - - phandle = acpi_device_handle(PCI_CONTROLLER(slot->pci_bus)->companion); - - if (acpi_bus_get_device(phandle, &pdevice)) { - pci_dbg(slot->pci_bus->self, "no parent device, assuming NULL\n"); - pdevice = NULL; - } - - acpi_scan_lock_acquire(); - /* - * Walk the rootbus node's immediate children looking for - * the slot's device node(s). There can be more than - * one for multifunction devices. - */ - for (;;) { - rethandle = NULL; - ret = acpi_get_next_object(ACPI_TYPE_DEVICE, - phandle, chandle, - &rethandle); - - if (ret == AE_NOT_FOUND || rethandle == NULL) - break; - - chandle = rethandle; - - ret = acpi_evaluate_integer(chandle, METHOD_NAME__ADR, - NULL, &adr); - - if (ACPI_SUCCESS(ret) && - (adr>>16) == (slot->device_num + 1)) { - - ret = acpi_bus_scan(chandle); - if (ACPI_FAILURE(ret)) { - printk(KERN_ERR "%s: acpi_bus_scan failed (0x%x) for slot %d func %d\n", - __func__, ret, (int)(adr>>16), - (int)(adr&0xffff)); - /* try to continue on */ - } - } - } - acpi_scan_lock_release(); - } - - pci_lock_rescan_remove(); - - /* Call the driver for the new device */ - pci_bus_add_devices(slot->pci_bus); - /* Call the drivers for the new devices subordinate to PPB */ - if (new_ppb) - pci_bus_add_devices(new_bus); - - pci_unlock_rescan_remove(); - mutex_unlock(&sn_hotplug_mutex); - - if (rc == 0) - pci_dbg(slot->pci_bus->self, "insert operation successful\n"); - else - pci_dbg(slot->pci_bus->self, "insert operation failed rc = %d\n", rc); - - return rc; -} - -static int disable_slot(struct hotplug_slot *bss_hotplug_slot) -{ - struct slot *slot = to_slot(bss_hotplug_slot); - struct pci_dev *dev, *temp; - int rc; - acpi_handle ssdt_hdl = NULL; - - /* Acquire update access to the bus */ - mutex_lock(&sn_hotplug_mutex); - - /* is it okay to bring this slot down? */ - rc = sn_slot_disable(bss_hotplug_slot, slot->device_num, - PCI_REQ_SLOT_ELIGIBLE); - if (rc) - goto leaving; - - /* free the ACPI resources for the slot */ - if (SN_ACPI_BASE_SUPPORT() && - PCI_CONTROLLER(slot->pci_bus)->companion) { - unsigned long long adr; - struct acpi_device *device; - acpi_handle phandle; - acpi_handle chandle = NULL; - acpi_handle rethandle; - acpi_status ret; - - /* Get the rootbus node pointer */ - phandle = acpi_device_handle(PCI_CONTROLLER(slot->pci_bus)->companion); - - acpi_scan_lock_acquire(); - /* - * Walk the rootbus node's immediate children looking for - * the slot's device node(s). There can be more than - * one for multifunction devices. - */ - for (;;) { - rethandle = NULL; - ret = acpi_get_next_object(ACPI_TYPE_DEVICE, - phandle, chandle, - &rethandle); - - if (ret == AE_NOT_FOUND || rethandle == NULL) - break; - - chandle = rethandle; - - ret = acpi_evaluate_integer(chandle, - METHOD_NAME__ADR, - NULL, &adr); - if (ACPI_SUCCESS(ret) && - (adr>>16) == (slot->device_num + 1)) { - /* retain the owner id */ - ssdt_hdl = chandle; - - ret = acpi_bus_get_device(chandle, - &device); - if (ACPI_SUCCESS(ret)) - acpi_bus_trim(device); - } - } - acpi_scan_lock_release(); - } - - pci_lock_rescan_remove(); - /* Free the SN resources assigned to the Linux device.*/ - list_for_each_entry_safe(dev, temp, &slot->pci_bus->devices, bus_list) { - if (PCI_SLOT(dev->devfn) != slot->device_num + 1) - continue; - - pci_dev_get(dev); - sn_bus_free_data(dev); - pci_stop_and_remove_bus_device(dev); - pci_dev_put(dev); - } - pci_unlock_rescan_remove(); - - /* Remove the SSDT for the slot from the ACPI namespace */ - if (SN_ACPI_BASE_SUPPORT() && ssdt_hdl) { - acpi_status ret; - ret = acpi_unload_parent_table(ssdt_hdl); - if (ACPI_FAILURE(ret)) { - acpi_handle_err(ssdt_hdl, - "%s: acpi_unload_parent_table failed (0x%x)\n", - __func__, ret); - /* try to continue on */ - } - } - - /* free the collected sysdata pointers */ - sn_bus_free_sysdata(); - - /* Deactivate slot */ - rc = sn_slot_disable(bss_hotplug_slot, slot->device_num, - PCI_REQ_SLOT_DISABLE); - leaving: - /* Release the bus lock */ - mutex_unlock(&sn_hotplug_mutex); - - return rc; -} - -static inline int get_power_status(struct hotplug_slot *bss_hotplug_slot, - u8 *value) -{ - struct slot *slot = to_slot(bss_hotplug_slot); - struct pcibus_info *pcibus_info; - u32 power; - - pcibus_info = SN_PCIBUS_BUSSOFT_INFO(slot->pci_bus); - mutex_lock(&sn_hotplug_mutex); - power = pcibus_info->pbi_enabled_devices & (1 << slot->device_num); - *value = power ? 1 : 0; - mutex_unlock(&sn_hotplug_mutex); - return 0; -} - -static void sn_release_slot(struct hotplug_slot *bss_hotplug_slot) -{ - kfree(to_slot(bss_hotplug_slot)); -} - -static int sn_hotplug_slot_register(struct pci_bus *pci_bus) -{ - int device; - struct pci_slot *pci_slot; - struct hotplug_slot *bss_hotplug_slot; - char name[SN_SLOT_NAME_SIZE]; - int rc = 0; - - /* - * Currently only four devices are supported, - * in the future there maybe more -- up to 32. - */ - - for (device = 0; device < SN_MAX_HP_SLOTS ; device++) { - if (sn_pci_slot_valid(pci_bus, device) != 1) - continue; - - if (sn_hp_slot_private_alloc(&bss_hotplug_slot, - pci_bus, device, name)) { - rc = -ENOMEM; - goto alloc_err; - } - bss_hotplug_slot->ops = &sn_hotplug_slot_ops; - - rc = pci_hp_register(bss_hotplug_slot, pci_bus, device, name); - if (rc) - goto register_err; - - pci_slot = bss_hotplug_slot->pci_slot; - rc = sysfs_create_file(&pci_slot->kobj, - &sn_slot_path_attr.attr); - if (rc) - goto alloc_err; - } - pci_dbg(pci_bus->self, "Registered bus with hotplug\n"); - return rc; - -register_err: - pci_dbg(pci_bus->self, "bus failed to register with err = %d\n", - rc); - - /* destroy THIS element */ - sn_hp_destroy(); - sn_release_slot(bss_hotplug_slot); - -alloc_err: - /* destroy anything else on the list */ - while ((bss_hotplug_slot = sn_hp_destroy())) { - pci_hp_deregister(bss_hotplug_slot); - sn_release_slot(bss_hotplug_slot); - } - - return rc; -} - -static int __init sn_pci_hotplug_init(void) -{ - struct pci_bus *pci_bus = NULL; - int rc; - int registered = 0; - - if (!sn_prom_feature_available(PRF_HOTPLUG_SUPPORT)) { - printk(KERN_ERR "%s: PROM version does not support hotplug.\n", - __func__); - return -EPERM; - } - - INIT_LIST_HEAD(&sn_hp_list); - - while ((pci_bus = pci_find_next_bus(pci_bus))) { - if (!pci_bus->sysdata) - continue; - - rc = sn_pci_bus_valid(pci_bus); - if (rc != 1) { - pci_dbg(pci_bus->self, "not a valid hotplug bus\n"); - continue; - } - pci_dbg(pci_bus->self, "valid hotplug bus\n"); - - rc = sn_hotplug_slot_register(pci_bus); - if (!rc) { - registered = 1; - } else { - registered = 0; - break; - } - } - - return registered == 1 ? 0 : -ENODEV; -} - -static void __exit sn_pci_hotplug_exit(void) -{ - struct hotplug_slot *bss_hotplug_slot; - - while ((bss_hotplug_slot = sn_hp_destroy())) { - pci_hp_deregister(bss_hotplug_slot); - sn_release_slot(bss_hotplug_slot); - } - - if (!list_empty(&sn_hp_list)) - printk(KERN_ERR "%s: internal list is not empty\n", __FILE__); -} - -module_init(sn_pci_hotplug_init); -module_exit(sn_pci_hotplug_exit); diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 80fe2d24fa37..e7982af9a5d8 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1025,10 +1025,15 @@ static void __pci_start_power_transition(struct pci_dev *dev, pci_power_t state) if (state == PCI_D0) { pci_platform_power_transition(dev, PCI_D0); /* - * Mandatory power management transition delays are - * handled in the PCIe portdrv resume hooks. + * Mandatory power management transition delays, see + * PCI Express Base Specification Revision 2.0 Section + * 6.6.1: Conventional Reset. Do not delay for + * devices powered on/off by corresponding bridge, + * because have already delayed for the bridge. */ if (dev->runtime_d3cold) { + if (dev->d3cold_delay && !dev->imm_ready) + msleep(dev->d3cold_delay); /* * When powering on a bridge from D3cold, the * whole hierarchy may be powered on into @@ -4602,16 +4607,14 @@ static int pci_pm_reset(struct pci_dev *dev, int probe) return pci_dev_wait(dev, "PM D3->D0", PCIE_RESET_READY_POLL_MS); } - /** - * pcie_wait_for_link_delay - Wait until link is active or inactive + * pcie_wait_for_link - Wait until link is active or inactive * @pdev: Bridge device * @active: waiting for active or inactive? - * @delay: Delay to wait after link has become active (in ms) * * Use this to wait till link becomes active or inactive. */ -bool pcie_wait_for_link_delay(struct pci_dev *pdev, bool active, int delay) +bool pcie_wait_for_link(struct pci_dev *pdev, bool active) { int timeout = 1000; bool ret; @@ -4648,25 +4651,13 @@ bool pcie_wait_for_link_delay(struct pci_dev *pdev, bool active, int delay) timeout -= 10; } if (active && ret) - msleep(delay); + msleep(100); else if (ret != active) pci_info(pdev, "Data Link Layer Link Active not %s in 1000 msec\n", active ? "set" : "cleared"); return ret == active; } -/** - * pcie_wait_for_link - Wait until link is active or inactive - * @pdev: Bridge device - * @active: waiting for active or inactive? - * - * Use this to wait till link becomes active or inactive. - */ -bool pcie_wait_for_link(struct pci_dev *pdev, bool active) -{ - return pcie_wait_for_link_delay(pdev, active, 100); -} - void pci_reset_secondary_bus(struct pci_dev *dev) { u16 ctrl; diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 0113343cfd1e..3f6947ee3324 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -528,7 +528,6 @@ static inline int pci_dev_specific_disable_acs_redir(struct pci_dev *dev) void pcie_do_recovery(struct pci_dev *dev, enum pci_channel_state state, u32 service); -bool pcie_wait_for_link_delay(struct pci_dev *pdev, bool active, int delay); bool pcie_wait_for_link(struct pci_dev *pdev, bool active); #ifdef CONFIG_PCIEASPM void pcie_aspm_init_link_state(struct pci_dev *pdev); diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 55a6951cedd1..652ef23bba35 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -1169,6 +1169,26 @@ static int pcie_aspm_get_policy(char *buffer, const struct kernel_param *kp) module_param_call(policy, pcie_aspm_set_policy, pcie_aspm_get_policy, NULL, 0644); +/** + * pcie_aspm_enabled - Check if PCIe ASPM has been enabled for a device. + * @pdev: Target device. + */ +bool pcie_aspm_enabled(struct pci_dev *pdev) +{ + struct pci_dev *bridge = pci_upstream_bridge(pdev); + bool ret; + + if (!bridge) + return false; + + mutex_lock(&aspm_lock); + ret = bridge->link_state ? !!bridge->link_state->aspm_enabled : false; + mutex_unlock(&aspm_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(pcie_aspm_enabled); + #ifdef CONFIG_PCIEASPM_DEBUG static ssize_t link_state_show(struct device *dev, struct device_attribute *attr, diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c index 308c3e0c4a34..1b330129089f 100644 --- a/drivers/pci/pcie/portdrv_core.c +++ b/drivers/pci/pcie/portdrv_core.c @@ -9,7 +9,6 @@ #include <linux/module.h> #include <linux/pci.h> #include <linux/kernel.h> -#include <linux/delay.h> #include <linux/errno.h> #include <linux/pm.h> #include <linux/pm_runtime.h> @@ -379,67 +378,6 @@ static int pm_iter(struct device *dev, void *data) return 0; } -static int get_downstream_delay(struct pci_bus *bus) -{ - struct pci_dev *pdev; - int min_delay = 100; - int max_delay = 0; - - list_for_each_entry(pdev, &bus->devices, bus_list) { - if (!pdev->imm_ready) - min_delay = 0; - else if (pdev->d3cold_delay < min_delay) - min_delay = pdev->d3cold_delay; - if (pdev->d3cold_delay > max_delay) - max_delay = pdev->d3cold_delay; - } - - return max(min_delay, max_delay); -} - -/* - * wait_for_downstream_link - Wait for downstream link to establish - * @pdev: PCIe port whose downstream link is waited - * - * Handle delays according to PCIe 4.0 section 6.6.1 before configuration - * access to the downstream component is permitted. - * - * This blocks PCI core resume of the hierarchy below this port until the - * link is trained. Should be called before resuming port services to - * prevent pciehp from starting to tear-down the hierarchy too soon. - */ -static void wait_for_downstream_link(struct pci_dev *pdev) -{ - int delay; - - if (pci_pcie_type(pdev) != PCI_EXP_TYPE_ROOT_PORT && - pci_pcie_type(pdev) != PCI_EXP_TYPE_DOWNSTREAM) - return; - - if (pci_dev_is_disconnected(pdev)) - return; - - if (!pdev->subordinate || list_empty(&pdev->subordinate->devices) || - !pdev->bridge_d3) - return; - - delay = get_downstream_delay(pdev->subordinate); - if (!delay) - return; - - dev_dbg(&pdev->dev, "waiting downstream link for %d ms\n", delay); - - /* - * If downstream port does not support speeds greater than 5 GT/s - * need to wait 100ms. For higher speeds (gen3) we need to wait - * first for the data link layer to become active. - */ - if (pcie_get_speed_cap(pdev) <= PCIE_SPEED_5_0GT) - msleep(delay); - else - pcie_wait_for_link_delay(pdev, true, delay); -} - /** * pcie_port_device_suspend - suspend port services associated with a PCIe port * @dev: PCI Express port to handle @@ -453,8 +391,6 @@ int pcie_port_device_suspend(struct device *dev) int pcie_port_device_resume_noirq(struct device *dev) { size_t off = offsetof(struct pcie_port_service_driver, resume_noirq); - - wait_for_downstream_link(to_pci_dev(dev)); return device_for_each_child(dev, &off, pm_iter); } @@ -485,8 +421,6 @@ int pcie_port_device_runtime_suspend(struct device *dev) int pcie_port_device_runtime_resume(struct device *dev) { size_t off = offsetof(struct pcie_port_service_driver, runtime_resume); - - wait_for_downstream_link(to_pci_dev(dev)); return device_for_each_child(dev, &off, pm_iter); } #endif /* PM */ diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 3bfa57d58402..3d5271a7a849 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -64,11 +64,6 @@ static struct resource *get_pci_domain_busn_res(int domain_nr) return &r->res; } -static int find_anything(struct device *dev, const void *data) -{ - return 1; -} - /* * Some device drivers need know if PCI is initiated. * Basically, we think PCI is not initiated when there @@ -79,7 +74,7 @@ int no_pci_devices(void) struct device *dev; int no_devices; - dev = bus_find_device(&pci_bus_type, NULL, NULL, find_anything); + dev = bus_find_next_device(&pci_bus_type, NULL); no_devices = (dev == NULL); put_device(dev); return no_devices; diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 304d98e0a96d..320255e5e8f8 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -5360,7 +5360,7 @@ static void quirk_reset_lenovo_thinkpad_p50_nvgpu(struct pci_dev *pdev) */ if (ioread32(map + 0x2240c) & 0x2) { pci_info(pdev, FW_BUG "GPU left initialized by EFI, resetting\n"); - ret = pci_reset_function(pdev); + ret = pci_reset_bus(pdev); if (ret < 0) pci_err(pdev, "Failed to reset GPU: %d\n", ret); } |