Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

PCI: keep ASPM link state consistent throughout PCIe hierarchy

In a PCIe hierarchy with a switch present, if the link state of an
endpoint device is changed, we must check the whole hierarchy from the
endpoint device to root port, and for each link in the hierarchy, the new
link state should be configured. Previously, the implementation checked
the state but forgot to configure the links between root port to switch.
Fixes Novell bz #448987.

Signed-off-by: Shaohua Li <shaohua.li@intel.com>
Tested-by: Andrew Patterson <andrew.patterson@hp.com>
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>

authored by

Shaohua Li and committed by
Jesse Barnes
46bbdfa4 2b8c2efe

+108 -21
+108 -21
drivers/pci/pcie/aspm.c
··· 33 33 struct pcie_link_state { 34 34 struct list_head sibiling; 35 35 struct pci_dev *pdev; 36 + bool downstream_has_switch; 37 + 38 + struct pcie_link_state *parent; 39 + struct list_head children; 40 + struct list_head link; 36 41 37 42 /* ASPM state */ 38 43 unsigned int support_state; ··· 130 125 link_state->clk_pm_enabled = !!enable; 131 126 } 132 127 133 - static void pcie_check_clock_pm(struct pci_dev *pdev) 128 + static void pcie_check_clock_pm(struct pci_dev *pdev, int blacklist) 134 129 { 135 130 int pos; 136 131 u32 reg32; ··· 154 149 if (!(reg16 & PCI_EXP_LNKCTL_CLKREQ_EN)) 155 150 enabled = 0; 156 151 } 157 - link_state->clk_pm_capable = capable; 158 152 link_state->clk_pm_enabled = enabled; 159 153 link_state->bios_clk_state = enabled; 160 - pcie_set_clock_pm(pdev, policy_to_clkpm_state(pdev)); 154 + if (!blacklist) { 155 + link_state->clk_pm_capable = capable; 156 + pcie_set_clock_pm(pdev, policy_to_clkpm_state(pdev)); 157 + } else { 158 + link_state->clk_pm_capable = 0; 159 + pcie_set_clock_pm(pdev, 0); 160 + } 161 + } 162 + 163 + static bool pcie_aspm_downstream_has_switch(struct pci_dev *pdev) 164 + { 165 + struct pci_dev *child_dev; 166 + 167 + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { 168 + if (child_dev->pcie_type == PCI_EXP_TYPE_UPSTREAM) 169 + return true; 170 + } 171 + return false; 161 172 } 162 173 163 174 /* ··· 440 419 { 441 420 struct pci_dev *child_dev; 442 421 443 - /* If no child, disable the link */ 422 + /* If no child, ignore the link */ 444 423 if (list_empty(&pdev->subordinate->devices)) 445 - return 0; 424 + return state; 446 425 list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { 447 426 if (child_dev->pcie_type == PCI_EXP_TYPE_PCI_BRIDGE) { 448 427 /* ··· 483 462 int valid = 1; 484 463 struct pcie_link_state *link_state = pdev->link_state; 485 464 465 + /* If no child, disable the link */ 466 + if (list_empty(&pdev->subordinate->devices)) 467 + state = 0; 486 468 /* 487 469 * if the downstream component has pci bridge function, don't do ASPM 488 470 * now ··· 517 493 link_state->enabled_state = state; 518 494 } 519 495 496 + static struct pcie_link_state *get_root_port_link(struct pcie_link_state *link) 497 + { 498 + struct pcie_link_state *root_port_link = link; 499 + while (root_port_link->parent) 500 + root_port_link = root_port_link->parent; 501 + return root_port_link; 502 + } 503 + 504 + /* check the whole hierarchy, and configure each link in the hierarchy */ 520 505 static void __pcie_aspm_configure_link_state(struct pci_dev *pdev, 521 506 unsigned int state) 522 507 { 523 508 struct pcie_link_state *link_state = pdev->link_state; 509 + struct pcie_link_state *root_port_link = get_root_port_link(link_state); 510 + struct pcie_link_state *leaf; 524 511 525 - if (link_state->support_state == 0) 526 - return; 527 512 state &= PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1; 528 513 529 - /* state 0 means disabling aspm */ 530 - state = pcie_aspm_check_state(pdev, state); 514 + /* check all links who have specific root port link */ 515 + list_for_each_entry(leaf, &link_list, sibiling) { 516 + if (!list_empty(&leaf->children) || 517 + get_root_port_link(leaf) != root_port_link) 518 + continue; 519 + state = pcie_aspm_check_state(leaf->pdev, state); 520 + } 521 + /* check root port link too in case it hasn't children */ 522 + state = pcie_aspm_check_state(root_port_link->pdev, state); 523 + 531 524 if (link_state->enabled_state == state) 532 525 return; 533 - __pcie_aspm_config_link(pdev, state); 526 + 527 + /* 528 + * we must change the hierarchy. See comments in 529 + * __pcie_aspm_config_link for the order 530 + **/ 531 + if (state & PCIE_LINK_STATE_L1) { 532 + list_for_each_entry(leaf, &link_list, sibiling) { 533 + if (get_root_port_link(leaf) == root_port_link) 534 + __pcie_aspm_config_link(leaf->pdev, state); 535 + } 536 + } else { 537 + list_for_each_entry_reverse(leaf, &link_list, sibiling) { 538 + if (get_root_port_link(leaf) == root_port_link) 539 + __pcie_aspm_config_link(leaf->pdev, state); 540 + } 541 + } 534 542 } 535 543 536 544 /* ··· 626 570 unsigned int state; 627 571 struct pcie_link_state *link_state; 628 572 int error = 0; 573 + int blacklist; 629 574 630 575 if (aspm_disabled || !pdev->is_pcie || pdev->link_state) 631 576 return; ··· 637 580 if (list_empty(&pdev->subordinate->devices)) 638 581 goto out; 639 582 640 - if (pcie_aspm_sanity_check(pdev)) 641 - goto out; 583 + blacklist = !!pcie_aspm_sanity_check(pdev); 642 584 643 585 mutex_lock(&aspm_lock); 644 586 645 587 link_state = kzalloc(sizeof(*link_state), GFP_KERNEL); 646 588 if (!link_state) 647 589 goto unlock_out; 590 + 591 + link_state->downstream_has_switch = pcie_aspm_downstream_has_switch(pdev); 592 + INIT_LIST_HEAD(&link_state->children); 593 + INIT_LIST_HEAD(&link_state->link); 594 + if (pdev->bus->self) {/* this is a switch */ 595 + struct pcie_link_state *parent_link_state; 596 + 597 + parent_link_state = pdev->bus->parent->self->link_state; 598 + if (!parent_link_state) { 599 + kfree(link_state); 600 + goto unlock_out; 601 + } 602 + list_add(&link_state->link, &parent_link_state->children); 603 + link_state->parent = parent_link_state; 604 + } 605 + 648 606 pdev->link_state = link_state; 649 607 650 - pcie_aspm_configure_common_clock(pdev); 651 - 652 - pcie_aspm_cap_init(pdev); 653 - 654 - /* config link state to avoid BIOS error */ 655 - state = pcie_aspm_check_state(pdev, policy_to_aspm_state(pdev)); 656 - __pcie_aspm_config_link(pdev, state); 657 - 658 - pcie_check_clock_pm(pdev); 608 + if (!blacklist) { 609 + pcie_aspm_configure_common_clock(pdev); 610 + pcie_aspm_cap_init(pdev); 611 + } else { 612 + link_state->enabled_state = PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1; 613 + link_state->bios_aspm_state = 0; 614 + /* Set support state to 0, so we will disable ASPM later */ 615 + link_state->support_state = 0; 616 + } 659 617 660 618 link_state->pdev = pdev; 661 619 list_add(&link_state->sibiling, &link_list); 620 + 621 + if (link_state->downstream_has_switch) { 622 + /* 623 + * If link has switch, delay the link config. The leaf link 624 + * initialization will config the whole hierarchy. but we must 625 + * make sure BIOS doesn't set unsupported link state 626 + **/ 627 + state = pcie_aspm_check_state(pdev, link_state->bios_aspm_state); 628 + __pcie_aspm_config_link(pdev, state); 629 + } else 630 + __pcie_aspm_configure_link_state(pdev, 631 + policy_to_aspm_state(pdev)); 632 + 633 + pcie_check_clock_pm(pdev, blacklist); 662 634 663 635 unlock_out: 664 636 if (error) ··· 721 635 /* All functions are removed, so just disable ASPM for the link */ 722 636 __pcie_aspm_config_one_dev(parent, 0); 723 637 list_del(&link_state->sibiling); 638 + list_del(&link_state->link); 724 639 /* Clock PM is for endpoint device */ 725 640 726 641 free_link_state(parent);