]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
ata: ahci: Allow ignoring the external/hotplug capability of ports
authorDamien Le Moal <dlemoal@kernel.org>
Thu, 21 Aug 2025 07:23:14 +0000 (16:23 +0900)
committerDamien Le Moal <dlemoal@kernel.org>
Mon, 25 Aug 2025 22:44:32 +0000 (07:44 +0900)
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>
drivers/ata/ahci.c
drivers/ata/ahci.h

index e1c24bbacf648c4e44b44e9b7245953a46ea95d5..7a7f88b3fa2b18074bc88959583c36745c83e75e 100644 (file)
@@ -689,40 +689,50 @@ MODULE_PARM_DESC(mask_port_map,
                 "where <pci_dev> is the PCI ID of an AHCI controller in the "
                 "form \"domain:bus:dev.func\"");
 
-static void ahci_apply_port_map_mask(struct device *dev,
-                                    struct ahci_host_priv *hpriv, char *mask_s)
+static char *ahci_mask_port_ext;
+module_param_named(mask_port_ext, ahci_mask_port_ext, charp, 0444);
+MODULE_PARM_DESC(mask_port_ext,
+                "32-bits mask to ignore the external/hotplug capability of ports. "
+                "Valid values are: "
+                "\"<mask>\" to apply the same mask to all AHCI controller "
+                "devices, and \"<pci_dev>=<mask>,<pci_dev>=<mask>,...\" to "
+                "specify different masks for the controllers specified, "
+                "where <pci_dev> is the PCI ID of an AHCI controller in the "
+                "form \"domain:bus:dev.func\"");
+
+static u32 ahci_port_mask(struct device *dev, char *mask_s)
 {
        unsigned int mask;
 
        if (kstrtouint(mask_s, 0, &mask)) {
                dev_err(dev, "Invalid port map mask\n");
-               return;
+               return 0;
        }
 
-       hpriv->mask_port_map = mask;
+       return mask;
 }
 
-static void ahci_get_port_map_mask(struct device *dev,
-                                  struct ahci_host_priv *hpriv)
+static u32 ahci_get_port_mask(struct device *dev, char *mask_p)
 {
        char *param, *end, *str, *mask_s;
        char *name;
+       u32 mask = 0;
 
-       if (!strlen(ahci_mask_port_map))
-               return;
+       if (!mask_p || !strlen(mask_p))
+               return 0;
 
-       str = kstrdup(ahci_mask_port_map, GFP_KERNEL);
+       str = kstrdup(mask_p, GFP_KERNEL);
        if (!str)
-               return;
+               return 0;
 
        /* Handle single mask case */
        if (!strchr(str, '=')) {
-               ahci_apply_port_map_mask(dev, hpriv, str);
+               mask = ahci_port_mask(dev, str);
                goto free;
        }
 
        /*
-        * Mask list case: parse the parameter to apply the mask only if
+        * Mask list case: parse the parameter to get the mask only if
         * the device name matches.
         */
        param = str;
@@ -752,11 +762,13 @@ static void ahci_get_port_map_mask(struct device *dev,
                        param++;
                }
 
-               ahci_apply_port_map_mask(dev, hpriv, mask_s);
+               mask = ahci_port_mask(dev, mask_s);
        }
 
 free:
        kfree(str);
+
+       return mask;
 }
 
 static void ahci_pci_save_initial_config(struct pci_dev *pdev,
@@ -782,8 +794,10 @@ static void ahci_pci_save_initial_config(struct pci_dev *pdev,
        }
 
        /* Handle port map masks passed as module parameter. */
-       if (ahci_mask_port_map)
-               ahci_get_port_map_mask(&pdev->dev, hpriv);
+       hpriv->mask_port_map =
+               ahci_get_port_mask(&pdev->dev, ahci_mask_port_map);
+       hpriv->mask_port_ext =
+               ahci_get_port_mask(&pdev->dev, ahci_mask_port_ext);
 
        ahci_save_initial_config(&pdev->dev, hpriv);
 }
@@ -1757,11 +1771,20 @@ static void ahci_mark_external_port(struct ata_port *ap)
        void __iomem *port_mmio = ahci_port_base(ap);
        u32 tmp;
 
-       /* mark external ports (hotplug-capable, eSATA) */
+       /*
+        * Mark external ports (hotplug-capable, eSATA), unless we were asked to
+        * ignore this feature.
+        */
        tmp = readl(port_mmio + PORT_CMD);
        if (((tmp & PORT_CMD_ESP) && (hpriv->cap & HOST_CAP_SXS)) ||
-           (tmp & PORT_CMD_HPCP))
+           (tmp & PORT_CMD_HPCP)) {
+               if (hpriv->mask_port_ext & (1U << ap->port_no)) {
+                       ata_port_info(ap,
+                               "Ignoring external/hotplug capability\n");
+                       return;
+               }
                ap->pflags |= ATA_PFLAG_EXTERNAL;
+       }
 }
 
 static void ahci_update_initial_lpm_policy(struct ata_port *ap)
index 2c10c8f440d122096aacd9f9bb28b9b3a5f5b71e..293b7fb216b51738e94998704998cec2100f2d0f 100644 (file)
@@ -330,6 +330,7 @@ struct ahci_host_priv {
        /* Input fields */
        unsigned int            flags;          /* AHCI_HFLAG_* */
        u32                     mask_port_map;  /* Mask of valid ports */
+       u32                     mask_port_ext;  /* Mask of ports ext capability */
 
        void __iomem *          mmio;           /* bus-independent mem map */
        u32                     cap;            /* cap to use */