pci: Sync PCIe downstream port LNKSTA on read
The PCIe link speed and width between a downstream device and its upstream port is negotiated on real hardware and susceptible to dynamic changes due to signal issues and power management. In the emulated device case there is no real hardware link, but we still might wish to have some consistency between endpoint and downstream port via a virtual negotiation. There is of course a real link for assigned devices and this same virtual negotiation allows the downstream port to match the endpoint, synchronizing on every read to support underlying physical hardware dynamically adjusting the link. This negotiation is intentionally unidirectional for compatibility. If the endpoint exceeds the capabilities of the downstream port or there is no endpoint device, the downstream port reports negotiation to its maximum speed and width, matching the previous case where negotiation was absent. De-tuning the endpoint to match a virtual link doesn't seem to benefit anyone and is a condition we've thus far reported without functional issues. Note that PCI_EXP_LNKSTA is already ignored for migration compatibility via pcie_cap_v1_fill(). Cc: Marcel Apfelbaum <marcel.apfelbaum@gmail.com> Tested-by: Geoffrey McRae <geoff@hostfission.com> Reviewed-by: Eric Auger <eric.auger@redhat.com> Signed-off-by: Alex Williamson <alex.williamson@redhat.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
This commit is contained in:
parent
d96a0ac71c
commit
727b48661f
@ -1353,6 +1353,10 @@ uint32_t pci_default_read_config(PCIDevice *d,
|
||||
{
|
||||
uint32_t val = 0;
|
||||
|
||||
if (pci_is_express_downstream_port(d) &&
|
||||
ranges_overlap(address, len, d->exp.exp_cap + PCI_EXP_LNKSTA, 2)) {
|
||||
pcie_sync_bridge_lnk(d);
|
||||
}
|
||||
memcpy(&val, d->config + address, len);
|
||||
return le32_to_cpu(val);
|
||||
}
|
||||
|
@ -741,6 +741,45 @@ void pcie_add_capability(PCIDevice *dev,
|
||||
memset(dev->cmask + offset, 0xFF, size);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sync the PCIe Link Status negotiated speed and width of a bridge with the
|
||||
* downstream device. If downstream device is not present, re-write with the
|
||||
* Link Capability fields. Limit width and speed to bridge capabilities for
|
||||
* compatibility. Use config_read to access the downstream device since it
|
||||
* could be an assigned device with volatile link information.
|
||||
*/
|
||||
void pcie_sync_bridge_lnk(PCIDevice *bridge_dev)
|
||||
{
|
||||
PCIBridge *br = PCI_BRIDGE(bridge_dev);
|
||||
PCIBus *bus = pci_bridge_get_sec_bus(br);
|
||||
PCIDevice *target = bus->devices[0];
|
||||
uint8_t *exp_cap = bridge_dev->config + bridge_dev->exp.exp_cap;
|
||||
uint16_t lnksta, lnkcap = pci_get_word(exp_cap + PCI_EXP_LNKCAP);
|
||||
|
||||
if (!target || !target->exp.exp_cap) {
|
||||
lnksta = lnkcap;
|
||||
} else {
|
||||
lnksta = target->config_read(target,
|
||||
target->exp.exp_cap + PCI_EXP_LNKSTA,
|
||||
sizeof(lnksta));
|
||||
|
||||
if ((lnksta & PCI_EXP_LNKSTA_NLW) > (lnkcap & PCI_EXP_LNKCAP_MLW)) {
|
||||
lnksta &= ~PCI_EXP_LNKSTA_NLW;
|
||||
lnksta |= lnkcap & PCI_EXP_LNKCAP_MLW;
|
||||
}
|
||||
|
||||
if ((lnksta & PCI_EXP_LNKSTA_CLS) > (lnkcap & PCI_EXP_LNKCAP_SLS)) {
|
||||
lnksta &= ~PCI_EXP_LNKSTA_CLS;
|
||||
lnksta |= lnkcap & PCI_EXP_LNKCAP_SLS;
|
||||
}
|
||||
}
|
||||
|
||||
pci_word_test_and_clear_mask(exp_cap + PCI_EXP_LNKSTA,
|
||||
PCI_EXP_LNKSTA_CLS | PCI_EXP_LNKSTA_NLW);
|
||||
pci_word_test_and_set_mask(exp_cap + PCI_EXP_LNKSTA, lnksta &
|
||||
(PCI_EXP_LNKSTA_CLS | PCI_EXP_LNKSTA_NLW));
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
* pci express extended capability helper functions
|
||||
*/
|
||||
|
@ -737,6 +737,19 @@ static inline int pci_is_express(const PCIDevice *d)
|
||||
return d->cap_present & QEMU_PCI_CAP_EXPRESS;
|
||||
}
|
||||
|
||||
static inline int pci_is_express_downstream_port(const PCIDevice *d)
|
||||
{
|
||||
uint8_t type;
|
||||
|
||||
if (!pci_is_express(d) || !d->exp.exp_cap) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
type = pcie_cap_get_type(d);
|
||||
|
||||
return type == PCI_EXP_TYPE_DOWNSTREAM || type == PCI_EXP_TYPE_ROOT_PORT;
|
||||
}
|
||||
|
||||
static inline uint32_t pci_config_size(const PCIDevice *d)
|
||||
{
|
||||
return pci_is_express(d) ? PCIE_CONFIG_SPACE_SIZE : PCI_CONFIG_SPACE_SIZE;
|
||||
|
@ -126,6 +126,7 @@ uint16_t pcie_find_capability(PCIDevice *dev, uint16_t cap_id);
|
||||
void pcie_add_capability(PCIDevice *dev,
|
||||
uint16_t cap_id, uint8_t cap_ver,
|
||||
uint16_t offset, uint16_t size);
|
||||
void pcie_sync_bridge_lnk(PCIDevice *dev);
|
||||
|
||||
void pcie_ari_init(PCIDevice *dev, uint16_t offset, uint16_t nextfn);
|
||||
void pcie_dev_ser_num_init(PCIDevice *dev, uint16_t offset, uint64_t ser_num);
|
||||
|
Loading…
Reference in New Issue
Block a user