ata: libahci: re-enable FBS after error handling on a port multiplier
PATCH v3 1/1 ata: libahci: re-enable FBS after error handling on a port multiplier
ahci_do_softreset() disables FBS before resetting a device behind a port multiplier, as required by AHCI 1.2 section 9.3.9, and re-enables it only when that softreset succeeds. If it fails, for example because a hot-plugged drive is still spinning up ("softreset failed (device not ready)"), EH escalates to a hardreset of the PMP link, and nothing turns FBS back on: only ahci_pmp_attach() does, which runs at the next boot. The port keeps working with command-based switching until then.
Seen on a Marvell 88SE9235 (1b4b:9235) with Marvell 88SM9705 port multipliers (1b4b:9705, in a Synology DX1222 expansion unit): after hot-plugging a drive, the kernel logged "FBS is disabled" followed by "softreset failed (device not ready)" and a PMP hardreset, and never "FBS is enabled" again (twice, on two different ports).
At the end of ahci_error_handler(), re-enable FBS if a PMP is still attached and FBS is supported but disabled.
Tested on 7.2.6 with the patch built as an out-of-tree libahci (the stock ahci module binds to it, symbol CRCs are unchanged). Hot-plugging a drive into a free port of the multiplier then logged "FBS is disabled", "softreset failed (device not ready)" and a PMP hardreset, followed by "re-enabling FBS after error handling" and "FBS is enabled". Afterwards two other drives behind the same multiplier read 259 + 259 MB/s in parallel (O_DIRECT, 4 MiB blocks), against 268 MB/s for one drive alone, so their commands overlap again.
---
Notes:
v3: no code changes. From and Signed-off-by now carry my full name instead of the GitHub handle "illofspeed" used for v2 (same author).
The controller is passed through (VFIO) to a KVM guest; the tested kernel is the guest's. The two earlier occurrences without the patch were on ata8 and ata10 of the same controller; this test was on ata7.
drivers/ata/libahci.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)
drivers/ata/libahci.c +17 -0
@@ -2233,6 +2233,23 @@ void ahci_error_handler(struct ata_port *ap)sata_pmp_error_handler (ap); +/* +* ahci_do_softreset() turns FBS off before resetting a fan-out link +* and turns it back on only if that reset succeeds. When it fails +* (e.g. a hot-plugged drive is still spinning up), EH escalates to a +* PMP hardreset, which never re-enables FBS, so the port would keep +* command-based switching until the PMP is attached again (reboot). +* Restore FBS once recovery is done and the PMP is still attached. +*/ +if (sata_pmp_attached (ap) &&ata_dev_enabled (ap->link.device)) { +struct ahci_port_priv *pp = ap->private_data; + +if (pp->fbs_supported && !pp->fbs_enabled) { +ata_port_info (ap,"re-enabling FBS after error handling\n" ); +ahci_enable_fbs (ap); + } + } +if (!ata_dev_enabled (ap->link.device)) hpriv->stop_engine (ap); }
Signature · 1 line
2.43.0
[email protected]
AI reviewThank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] ata: ahci_enable_fbs() modifies PxFBS.EN without clearing PxCMD.FRE (AHCI spec violation) --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] ata: ahci_enable_fbs() modifies PxFBS.EN without clearing PxCMD.FRE (AHCI spec violation) --
commit 9f8000187fcdaf491c78af72f7fe5bba9f1d33a6
Author: Kyrylo Biryuk <[email protected]>
ata: libahci: re-enable FBS after error handling on a port multiplier
This commit re-enables FBS at the end of the error handler for port multipliers. It addresses an issue where a softreset failure escalates to a PMP hardreset, which leaves FBS disabled until the next boot.
Quoted 11 lines
drivers/ata/libahci.c +4
-0@@ -2221,6 +2221,23 @@ void ahci_error_handler(struct ata_port *ap)sata_pmp_error_handler (ap); +/* +* ahci_do_softreset() turns FBS off before resetting a fan-out link +* and turns it back on only if that reset succeeds. When it fails +* (e.g. a hot-plugged drive is still spinning up), EH escalates to a
drivers/ata/libahci.c void ahci_error_handler(struct ata_port *ap) · line 2237
Quoted 10 lines
drivers/ata/libahci.c +10
-0+* PMP hardreset, which never re-enables FBS, so the port would keep +* command-based switching until the PMP is attached again (reboot). +* Restore FBS once recovery is done and the PMP is still attached. +*/ +if (sata_pmp_attached (ap) &&ata_dev_enabled (ap->link.device)) { +struct ahci_port_priv *pp = ap->private_data; + +if (pp->fbs_supported && !pp->fbs_enabled) { +ata_port_info (ap,"re-enabling FBS after error handling\n" ); +ahci_enable_fbs (ap);
[Severity: Medium]
This problem wasn't introduced by this patch, but this new call to ahci_enable_fbs() exposes a preexisting AHCI spec violation.
AHCI Spec 1.2+ section 9.3.6 requires that PxFBS.EN must not be set to '1' while PxCMD.ST or PxCMD.FRE are '1'.
In ahci_enable_fbs(), we call hpriv->stop_engine():
drivers/ata/libahci.c:ahci_enable_fbs() {
...
rc = hpriv->stop_engine(ap);
if (rc)
return;
writel(fbs | PORT_FBS_EN, port_mmio + PORT_FBS);
...
}However, stop_engine() only clears PxCMD.ST (PORT_CMD_START). It does not clear PxCMD.FRE (PORT_CMD_FIS_RX).
Since ahci_stop_fis_rx() is not called here to clear PORT_CMD_FIS_RX, toggling PORT_FBS_EN modifies PxFBS.EN while PxCMD.FRE is still '1'.
Does this run the risk of causing controller instability or command failures on strict hardware implementations?
Quoted 6 lines
drivers/ata/libahci.c +3
-0+ } + } +if (!ata_dev_enabled (ap->link.device)) hpriv->stop_engine (ap); }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
An automated review (Sashiko) of this patch pointed out that ahci_enable_fbs() writes PxFBS.EN after hpriv->stop_engine(), which clears PxCMD.ST but leaves PxCMD.FRE set, and asked whether that could upset strict controllers.
For the record: this patch only adds a third caller of the existing helper. ahci_pmp_attach() (every boot with a port multiplier, after ahci_start_port() has enabled FIS reception) and ahci_do_softreset() (after a successful fan-out softreset) already call ahci_enable_fbs() with the port in the same state, so the patch does not change when or how PxFBS.EN is written. If the helper should also stop FIS reception around the write, that would be a separate change to ahci_enable_fbs() and ahci_disable_fbs(); I am happy to look into it if the maintainers think it is worth doing.
Tested on the 88SE9235 + 88SM9705 setup from the commit message: the hot-plug now ends with "re-enabling FBS after error handling" and "FBS is enabled", and parallel reads behind the multiplier scale again.
Niklas Cassel
maintainerQuoted 15 lines · Kyrylo Biryuk
An automated review (Sashiko) of this patch pointed out that
ahci_enable_fbs()writes PxFBS.EN afterhpriv->stop_engine(), which clears PxCMD.ST but leaves PxCMD.FRE set, and asked whether that could upset strict controllers.For the record: this patch only adds a third caller of the existing helper.
ahci_pmp_attach()(every boot with a port multiplier, afterahci_start_port()has enabled FIS reception) andahci_do_softreset()(after a successful fan-out softreset) already callahci_enable_fbs()with the port in the same state, so the patch does not change when or how PxFBS.EN is written. If the helper should also stop FIS reception around the write, that would be a separate change toahci_enable_fbs()andahci_disable_fbs(); I am happy to look into it if the maintainers think it is worth doing.
Quoted 3 lines
Tested on the 88SE9235 + 88SM9705 setup from the commit message: the hot-plug now ends with "re-enabling FBS after error handling" and "FBS is enabled", and parallel reads behind the multiplier scale again.
Looking at:
3.3.16 Offset 40h: PxFBS: Port x FIS-based Switching Control
Enable (EN): When set to ‘1’, a Port Multiplier is attached and the HBA shall use FIS-based switching to communicate with it. When cleared to ‘0’, FIS-based switching is not being used. Software shall only change the value of the EN bit when PxCMD.ST is cleared to ‘0’
PxCMD.ST will be cleared to 0 by the hpriv->stop_engine() in ahci_enable_fbs().
I don't see any mention that PxCMD.FRE needs to be disabled in addition to PxCMD.ST.
Presumably, if PxCMD.ST is disabled, why would you receive any FIS?
Sure the spec says that PxCMD.FRE should be set before PxCMD.ST, but I can't find anywhere in the spec where it says that you need to clear PxCMD.FRE before each time you re-start the engine (set PxCMD.ST).
So I think Sashiko is hallucinating here.
Kind regards,
Niklas
Niklas Cassel
maintainerQuoted 17 lines · Kyrylo Biryuk
ahci_do_softreset()disables FBS before resetting a device behind a port multiplier, as required by AHCI 1.2 section 9.3.9, and re-enables it only when that softreset succeeds. If it fails, for example because a hot-plugged drive is still spinning up ("softreset failed (device not ready)"), EH escalates to a hardreset of the PMP link, and nothing turns FBS back on: onlyahci_pmp_attach()does, which runs at the next boot. The port keeps working with command-based switching until then.Seen on a Marvell 88SE9235 (1b4b:9235) with Marvell 88SM9705 port multipliers (1b4b:9705, in a Synology DX1222 expansion unit): after hot-plugging a drive, the kernel logged "FBS is disabled" followed by "softreset failed (device not ready)" and a PMP hardreset, and never "FBS is enabled" again (twice, on two different ports).
At the end of
ahci_error_handler(), re-enable FBS if a PMP is still attached and FBS is supported but disabled.
Quoted 8 lines
Tested on 7.2.6 with the patch built as an out-of-tree libahci (the stock ahci module binds to it, symbol CRCs are unchanged). Hot-plugging a drive into a free port of the multiplier then logged "FBS is disabled", "softreset failed (device not ready)" and a PMP hardreset, followed by "re-enabling FBS after error handling" and "FBS is enabled". Afterwards two other drives behind the same multiplier read 259 + 259 MB/s in parallel (
O_DIRECT, 4 MiB blocks), against 268 MB/s for one drive alone, so their commands overlap again.
This whole testing paragraph belongs after the '---'.
Having specific information about exact kernel version, or if the module was built as an out of tree module, or built-in, is not information that should be in the commit log itself.
Quoted 28 lines
Fixes 89dafa20f3da ("ahci: disabled FBS prior to issuing software reset")Assisted-by LLMSigned-off-by Kyrylo Biryuk <[email protected]>---
Notes:
v3: no code changes. From and Signed-off-by now carry my full name instead of the GitHub handle "illofspeed" used for v2 (same author).The controller is passed through (VFIO) to a KVM guest; the tested kernel is the guest's. The two earlier occurrences without the patch were on ata8 and ata10 of the same controller; this test was on ata7.
drivers/ata/libahci.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)drivers/ata/libahci.c +3
-0@@ -2233,6 +2233,23 @@ void ahci_error_handler(struct ata_port *ap)sata_pmp_error_handler (ap); +/* +* ahci_do_softreset() turns FBS off before resetting a fan-out link +* and turns it back on only if that reset succeeds. When it fails
drivers/ata/libahci.c void ahci_error_handler(struct ata_port *ap) · line 2248
Quoted 10 lines
drivers/ata/libahci.c +10
-0+* (e.g. a hot-plugged drive is still spinning up), EH escalates to a +* PMP hardreset, which never re-enables FBS, so the port would keep +* command-based switching until the PMP is attached again (reboot). +* Restore FBS once recovery is done and the PMP is still attached. +*/ +if (sata_pmp_attached (ap) &&ata_dev_enabled (ap->link.device)) { +struct ahci_port_priv *pp = ap->private_data; + +if (pp->fbs_supported && !pp->fbs_enabled) { +ata_port_info (ap,"re-enabling FBS after error handling\n" );
I would drop this print.
Kind regards,
Niklas