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

ata: ahci: Allow ignoring the external/hotplug capability of ports

Commit 4edf1505b76d ("ata: ahci: Disallow LPM policy control for
external ports") introduced disabling link power management (LPM) for
ports that are advertized as external/hotplug capable. This is necessary
to force the maximum power policy (ATA_LPM_MAX_POWER) onto the port link
to ensure that the hotplug capability of the port is functional.

However, doing so blindly for all ports can prevent systems from going
into a low power state, even if the external/hotplug ports on the system
are unused. E.g., a laptop may see the internal SATA slot of a docking
station as an external hotplug capable port, and in such case, the user
may prefer to not use the port and to favor instead enabling LPM
to allow the laptop to transition to low power states.

Since there is no easy method to automatically detect such choice,
introduce the new mask_port_ext module parameter to allow a user to
ignore the external/hotplug capability of a port. The format for this
parameter value is identical to the format used for the mask_port_map
parameter: a mask can be defined for all AHCI adapters of a system or
for a particular adapters identified with their PCI IDs (bus:dev.func
format).

The function ahci_get_port_map_mask() is renamed to ahci_get_port_mask()
and modified to return a mask, either for the port map mask of an
adapter (to ignore ports) or for the external/hotplug capability of an
adapter. Differentiation between map_port_mask and map_port_ext_mask is
done by passing the parameter string to ahci_get_port_mask() as a second
argument.

To be consistent with this change, the function
ahci_apply_port_map_mask() is renamed ahci_port_mask() and changed to
return a mask value.

The mask for the external/hotplug capability for an adapter, if defined
by the map_port_ext_mask parameter, is stored in the new field
mask_port_ext of struct ahci_host_priv. ahci_mark_external_port() is
modified to not set the ATA_PFLAG_EXTERNAL flag for a port if
hpriv->mask_port_ext includes the number of the port. In such case,
an information message is printed to notify that the external/hotplug
capability is being ignored.

Reported-by: Dieter Mummenschanz <dmummenschanz@web.de>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220465
Fixes: 4edf1505b76d ("ata: ahci: Disallow LPM policy control for external ports")
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
Tested-by: Dieter Mummenschanz <dmummenschanz@web.de>

+41 -17
+40 -17
drivers/ata/ahci.c
··· 689 689 "where <pci_dev> is the PCI ID of an AHCI controller in the " 690 690 "form \"domain:bus:dev.func\""); 691 691 692 - static void ahci_apply_port_map_mask(struct device *dev, 693 - struct ahci_host_priv *hpriv, char *mask_s) 692 + static char *ahci_mask_port_ext; 693 + module_param_named(mask_port_ext, ahci_mask_port_ext, charp, 0444); 694 + MODULE_PARM_DESC(mask_port_ext, 695 + "32-bits mask to ignore the external/hotplug capability of ports. " 696 + "Valid values are: " 697 + "\"<mask>\" to apply the same mask to all AHCI controller " 698 + "devices, and \"<pci_dev>=<mask>,<pci_dev>=<mask>,...\" to " 699 + "specify different masks for the controllers specified, " 700 + "where <pci_dev> is the PCI ID of an AHCI controller in the " 701 + "form \"domain:bus:dev.func\""); 702 + 703 + static u32 ahci_port_mask(struct device *dev, char *mask_s) 694 704 { 695 705 unsigned int mask; 696 706 697 707 if (kstrtouint(mask_s, 0, &mask)) { 698 708 dev_err(dev, "Invalid port map mask\n"); 699 - return; 709 + return 0; 700 710 } 701 711 702 - hpriv->mask_port_map = mask; 712 + return mask; 703 713 } 704 714 705 - static void ahci_get_port_map_mask(struct device *dev, 706 - struct ahci_host_priv *hpriv) 715 + static u32 ahci_get_port_mask(struct device *dev, char *mask_p) 707 716 { 708 717 char *param, *end, *str, *mask_s; 709 718 char *name; 719 + u32 mask = 0; 710 720 711 - if (!strlen(ahci_mask_port_map)) 712 - return; 721 + if (!mask_p || !strlen(mask_p)) 722 + return 0; 713 723 714 - str = kstrdup(ahci_mask_port_map, GFP_KERNEL); 724 + str = kstrdup(mask_p, GFP_KERNEL); 715 725 if (!str) 716 - return; 726 + return 0; 717 727 718 728 /* Handle single mask case */ 719 729 if (!strchr(str, '=')) { 720 - ahci_apply_port_map_mask(dev, hpriv, str); 730 + mask = ahci_port_mask(dev, str); 721 731 goto free; 722 732 } 723 733 724 734 /* 725 - * Mask list case: parse the parameter to apply the mask only if 735 + * Mask list case: parse the parameter to get the mask only if 726 736 * the device name matches. 727 737 */ 728 738 param = str; ··· 762 752 param++; 763 753 } 764 754 765 - ahci_apply_port_map_mask(dev, hpriv, mask_s); 755 + mask = ahci_port_mask(dev, mask_s); 766 756 } 767 757 768 758 free: 769 759 kfree(str); 760 + 761 + return mask; 770 762 } 771 763 772 764 static void ahci_pci_save_initial_config(struct pci_dev *pdev, ··· 794 782 } 795 783 796 784 /* Handle port map masks passed as module parameter. */ 797 - if (ahci_mask_port_map) 798 - ahci_get_port_map_mask(&pdev->dev, hpriv); 785 + hpriv->mask_port_map = 786 + ahci_get_port_mask(&pdev->dev, ahci_mask_port_map); 787 + hpriv->mask_port_ext = 788 + ahci_get_port_mask(&pdev->dev, ahci_mask_port_ext); 799 789 800 790 ahci_save_initial_config(&pdev->dev, hpriv); 801 791 } ··· 1771 1757 void __iomem *port_mmio = ahci_port_base(ap); 1772 1758 u32 tmp; 1773 1759 1774 - /* mark external ports (hotplug-capable, eSATA) */ 1760 + /* 1761 + * Mark external ports (hotplug-capable, eSATA), unless we were asked to 1762 + * ignore this feature. 1763 + */ 1775 1764 tmp = readl(port_mmio + PORT_CMD); 1776 1765 if (((tmp & PORT_CMD_ESP) && (hpriv->cap & HOST_CAP_SXS)) || 1777 - (tmp & PORT_CMD_HPCP)) 1766 + (tmp & PORT_CMD_HPCP)) { 1767 + if (hpriv->mask_port_ext & (1U << ap->port_no)) { 1768 + ata_port_info(ap, 1769 + "Ignoring external/hotplug capability\n"); 1770 + return; 1771 + } 1778 1772 ap->pflags |= ATA_PFLAG_EXTERNAL; 1773 + } 1779 1774 } 1780 1775 1781 1776 static void ahci_update_initial_lpm_policy(struct ata_port *ap)
+1
drivers/ata/ahci.h
··· 330 330 /* Input fields */ 331 331 unsigned int flags; /* AHCI_HFLAG_* */ 332 332 u32 mask_port_map; /* Mask of valid ports */ 333 + u32 mask_port_ext; /* Mask of ports ext capability */ 333 334 334 335 void __iomem * mmio; /* bus-independent mem map */ 335 336 u32 cap; /* cap to use */