NTB: epf: serialize MW setup and report reliable errors
v2 cover NTB: epf: serialize MW setup and report reliable errors
This series fixes two related NTB EPF memory-window issues.
The first patch serializes shared MW parameter writes with command submission, preventing concurrent setups from using each other's DMA addresses.
The second patch propagates definite endpoint command errors while completion status can still be associated with the current command.
Changes since v1:
- koichiro pointed out that an endpoint may complete CONFIGURE_MW after the host times out. Propagating -ETIMEDOUT can cause clients to free DMA memory while the endpoint is still configuring the mapping.
Returning success only for the timed-out command avoids that immediate regression, but was insufficient in the end b/c a late error from command A may be observed during command B. Returning that stale error can cause B's buffer to be freed before B completes.
The second patch records whether any command has timed out and preserves the original MW return behavior thereafter.
The first patch also prevents a pre-timeout regression: without parameter serialization, propagating an explicit rejection from one setup can release memory referenced by another concurrent setup.
v1: https://lists.openwall.net/linux-kernel/2026/10/27/2144
Review: https://lists.openwall.net/linux-kernel/2026/10/28/466
Avi Weiss (2):
NTB: epf: serialize memory window parameter writes
NTB: epf: propagate MW command errors while status is reliable
drivers/ntb/hw/epf/ntb_hw_epf.c | 37 ++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-)
base-commit: cbbddb6b08cc9900a87cccaa9b22bbe06aa4f50a
Signature · 1 line
2.43.0
PATCH v2 1/2 NTB: epf: serialize memory window parameter writes
ntb_epf_mw_set_trans() writes the address and size to shared control registers before ntb_epf_send_command() acquires cmd_lock. Concurrent memory window setup requests can overwrite each other's parameters, causing one window to use another's DMA address.
Hold cmd_lock across the parameter writes and command submission. Use a locked command helper to preserve the existing serialization for other command callers.
--- drivers/ntb/hw/epf/ntb_hw_epf.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-)
drivers/ntb/hw/epf/ntb_hw_epf.c +16 -4
@@ -123,15 +123,14 @@ struct ntb_epf_dev {#define ntb_ndev (__ntb) container_of(__ntb, struct ntb_epf_dev, ntb) -static int ntb_epf_send_command (struct ntb_epf_dev *ndev,u32 command, -u32 argument) +static int ntb_epf_send_command_locked (struct ntb_epf_dev *ndev,u32 command, +u32 argument) {ktime_t timeout;bool timedout;int ret =0 ;u32 status; -mutex_lock (&ndev->cmd_lock);writel (argument, ndev->ctrl_reg +NTB_EPF_ARGUMENT );writel (command, ndev->ctrl_reg +NTB_EPF_COMMAND );
@@ -157,6 +156,17 @@ static int ntb_epf_send_command(struct ntb_epf_dev *ndev, u32 command, }writew (0 , ndev->ctrl_reg +NTB_EPF_CMD_STATUS ); + +return ret; +} + +static int ntb_epf_send_command (struct ntb_epf_dev *ndev,u32 command, +u32 argument) +{ +int ret; + +mutex_lock (&ndev->cmd_lock); + ret =ntb_epf_send_command_locked (ndev, command, argument);mutex_unlock (&ndev->cmd_lock);return ret;
@@ -494,11 +504,13 @@ static int ntb_epf_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx,return -EINVAL ; } +mutex_lock (&ndev->cmd_lock);writel (lower_32_bits (addr), ndev->ctrl_reg +NTB_EPF_LOWER_ADDR );writel (upper_32_bits (addr), ndev->ctrl_reg +NTB_EPF_UPPER_ADDR );writel (lower_32_bits (size), ndev->ctrl_reg +NTB_EPF_LOWER_SIZE );writel (upper_32_bits (size), ndev->ctrl_reg +NTB_EPF_UPPER_SIZE ); -ntb_epf_send_command (ndev,CMD_CONFIGURE_MW , idx); +ntb_epf_send_command_locked (ndev,CMD_CONFIGURE_MW , idx); +mutex_unlock (&ndev->cmd_lock);return 0 ; }
Signature · 1 line
2.43.0
Re: [PATCH v2 1/2] NTB: epf: serialize memory window parameter writes
Quoted 11 lines · Avi Weiss
ntb_epf_mw_set_trans()writes the address and size to shared control registers beforentb_epf_send_command()acquirescmd_lock. Concurrent memory window setup requests can overwrite each other's parameters, causing one window to use another's DMA address.Hold
cmd_lockacross the parameter writes and command submission. Use a locked command helper to preserve the existing serialization for other command callers.Fixes 812ce2f8d14e ("NTB: Add support for EPF PCI Non-Transparent Bridge")Signed-off-by Avi Weiss <[email protected]>
Quoted 62 lines
--- drivers/ntb/hw/epf/ntb_hw_epf.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-)drivers/ntb/hw/epf/ntb_hw_epf.c +16
-4@@ -123,15 +123,14 @@ struct ntb_epf_dev {#define ntb_ndev (__ntb) container_of(__ntb, struct ntb_epf_dev, ntb) -static int ntb_epf_send_command (struct ntb_epf_dev *ndev,u32 command, -u32 argument) +static int ntb_epf_send_command_locked (struct ntb_epf_dev *ndev,u32 command, +u32 argument) {ktime_t timeout;bool timedout;int ret =0 ;u32 status; -mutex_lock (&ndev->cmd_lock);writel (argument, ndev->ctrl_reg +NTB_EPF_ARGUMENT );writel (command, ndev->ctrl_reg +NTB_EPF_COMMAND );@@ -157,6 +156,17 @@ static int ntb_epf_send_command(struct ntb_epf_dev *ndev, u32 command, }writew (0 , ndev->ctrl_reg +NTB_EPF_CMD_STATUS ); + +return ret; +} + +static int ntb_epf_send_command (struct ntb_epf_dev *ndev,u32 command, +u32 argument) +{ +int ret; + +mutex_lock (&ndev->cmd_lock); + ret =ntb_epf_send_command_locked (ndev, command, argument);mutex_unlock (&ndev->cmd_lock);return ret;@@ -494,11 +504,13 @@ static int ntb_epf_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx,return -EINVAL ; } +mutex_lock (&ndev->cmd_lock);writel (lower_32_bits (addr), ndev->ctrl_reg +NTB_EPF_LOWER_ADDR );writel (upper_32_bits (addr), ndev->ctrl_reg +NTB_EPF_UPPER_ADDR );writel (lower_32_bits (size), ndev->ctrl_reg +NTB_EPF_LOWER_SIZE );writel (upper_32_bits (size), ndev->ctrl_reg +NTB_EPF_UPPER_SIZE ); -ntb_epf_send_command (ndev,CMD_CONFIGURE_MW , idx); +ntb_epf_send_command_locked (ndev,CMD_CONFIGURE_MW , idx); +mutex_unlock (&ndev->cmd_lock);return 0 ; }Signature · 1 line
2.43.0
PATCH v2 2/2 NTB: epf: propagate MW command errors while status is reliable
In reply to Avi Weissntb_epf_mw_set_trans() and ntb_epf_mw_clear_trans() discard command errors even when the endpoint explicitly rejects a request.
A timed-out command may still complete on the endpoint. Propagating that timeout can cause callers to free DMA buffers before the mapping completes. A late completion may also be mistaken for the result of a subsequent command.
Record the first command timeout and propagate MW command errors only while completion status remains reliable. Preserve the original MW success-return behavior after a timeout to avoid releasing buffers based on ambiguous results.
--- drivers/ntb/hw/epf/ntb_hw_epf.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-)
drivers/ntb/hw/epf/ntb_hw_epf.c +14 -5
@@ -102,6 +102,7 @@ struct ntb_epf_dev {struct device *dev;/* Mutex to protect providing commands to NTB EPF */ struct mutex cmd_lock; +bool cmd_status_unreliable;const enum pci_barno *barno_map;
@@ -148,6 +149,7 @@ static int ntb_epf_send_command_locked(struct ntb_epf_dev *ndev, u32 command,break ;if (WARN_ON (timedout)) { + ndev->cmd_status_unreliable = true; ret = -ETIMEDOUT ;break ; }
@@ -156,6 +158,13 @@ static int ntb_epf_send_command_locked(struct ntb_epf_dev *ndev, u32 command, }writew (0 , ndev->ctrl_reg +NTB_EPF_CMD_STATUS ); +/* +* After a timeout, a status may refer to an earlier command. +* MW callers may free buffers on error, so retain old semantics. +*/ +if (ndev->cmd_status_unreliable && + (command ==CMD_CONFIGURE_MW || command ==CMD_TEARDOWN_MW )) + ret =0 ;return ret; }
@@ -485,7 +494,7 @@ static int ntb_epf_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx,struct ntb_epf_dev *ndev =ntb_ndev (ntb);struct device *dev = ndev->dev;resource_size_t mw_size; -int bar; +int bar, ret;if (pidx !=NTB_DEF_PEER_IDX ) {dev_err (dev,"Unsupported Peer ID %d\n" , pidx);
@@ -509,19 +518,19 @@ static int ntb_epf_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx,writel (upper_32_bits (addr), ndev->ctrl_reg +NTB_EPF_UPPER_ADDR );writel (lower_32_bits (size), ndev->ctrl_reg +NTB_EPF_LOWER_SIZE );writel (upper_32_bits (size), ndev->ctrl_reg +NTB_EPF_UPPER_SIZE ); -ntb_epf_send_command_locked (ndev,CMD_CONFIGURE_MW , idx); + ret =ntb_epf_send_command_locked (ndev,CMD_CONFIGURE_MW , idx);mutex_unlock (&ndev->cmd_lock); -return 0 ; +return ret; }static int ntb_epf_mw_clear_trans (struct ntb_dev *ntb,int pidx,int idx) {struct ntb_epf_dev *ndev =ntb_ndev (ntb);struct device *dev = ndev->dev; -int ret =0 ; +int ret; -ntb_epf_send_command (ndev,CMD_TEARDOWN_MW , idx); + ret =ntb_epf_send_command (ndev,CMD_TEARDOWN_MW , idx);if (ret)dev_err (dev,"Failed to teardown memory window\n" );
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 2/2] NTB: epf: propagate MW command errors while status is reliable
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] NTB: epf: ntb_epf_send_command_locked() masks MW timeouts and errors - [Medium] ntb: epf: dead…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] NTB: epf: ntb_epf_send_command_locked() masks MW timeouts and errors
- [Medium] ntb: epf: dead error handling for CMD_TEARDOWN_MW in ntb_epf_mw_clear_trans() --
commit ac4b0e780171195c4ee0fe212bcbbbd97ae600ff
Author: Avi Weiss <[email protected]>
NTB: epf: propagate MW command errors while status is reliable
This commit propagates command errors from ntb_epf_mw_set_trans() and ntb_epf_mw_clear_trans() while tracking command timeouts in ndev->cmd_status_unreliable so that memory window operations continue to return 0 once a timeout has occurred.
Quoted 24 lines
drivers/ntb/hw/epf/ntb_hw_epf.c +2
-0@@ -102,6 +102,7 @@ struct ntb_epf_dev {struct device *dev;/* Mutex to protect providing commands to NTB EPF */ struct mutex cmd_lock; +bool cmd_status_unreliable;const enum pci_barno *barno_map;@@ -148,6 +149,7 @@ static int ntb_epf_send_command_locked(struct ntb_epf_dev *ndev, u32 command,break ;if (WARN_ON (timedout)) { + ndev->cmd_status_unreliable = true; ret = -ETIMEDOUT ;break ; }@@ -156,6 +158,13 @@ static int ntb_epf_send_command_locked(struct ntb_epf_dev *ndev, u32 command, }writew (0 , ndev->ctrl_reg +NTB_EPF_CMD_STATUS );
drivers/ntb/hw/epf/ntb_hw_epf.c static int ntb_epf_send_command_locked(struct ntb_epf_dev *ndev, u32 command, · line 170
Quoted 10 lines
drivers/ntb/hw/epf/ntb_hw_epf.c +7
-0+/* +* After a timeout, a status may refer to an earlier command. +* MW callers may free buffers on error, so retain old semantics. +*/ +if (ndev->cmd_status_unreliable && + (command ==CMD_CONFIGURE_MW || command ==CMD_TEARDOWN_MW )) + ret =0 ;return ret; }
[Severity: High]
When CMD_CONFIGURE_MW itself times out in ntb_epf_send_command_locked() in drivers/ntb/hw/epf/ntb_hw_epf.c, setting ndev->cmd_status_unreliable = true causes the check right after the loop to immediately overwrite ret = -ETIMEDOUT with 0.
In ntb_epf_mw_set_trans(), doesn't returning 0 on a CMD_CONFIGURE_MW timeout release ndev->cmd_lock while the timed-out command is still pending in ndev->ctrl_reg, allowing multi-window setup loops such as ntb_transport_link_work() -> ntb_set_mw() -> ntb_mw_set_trans() and ntb_msi_setup_mws() -> ntb_mw_set_trans() to immediately advance to the next memory window and overwrite NTB_EPF_LOWER_ADDR, NTB_EPF_UPPER_ADDR, NTB_EPF_LOWER_SIZE, NTB_EPF_UPPER_SIZE, NTB_EPF_ARGUMENT, and NTB_EPF_COMMAND?
Callers such as ntb_set_mw(), perf_setup_inbuf(), tool_setup_mw(), and ntb_msi_setup_mws() also treat the 0 return value as a valid mapping and bring up the NTB link with an unconfigured inbound BAR window. In addition, because ndev->cmd_status_unreliable is never cleared once any command times out (including CMD_LINK_UP or CMD_LINK_DOWN), won't all subsequent ntb_epf_mw_set_trans() calls permanently mask explicit COMMAND_STATUS_ERROR (-EINVAL) rejections from the endpoint as well?
Quoted 22 lines
drivers/ntb/hw/epf/ntb_hw_epf.c +3
-3@@ -485,7 +494,7 @@ static int ntb_epf_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx,struct ntb_epf_dev *ndev =ntb_ndev (ntb);struct device *dev = ndev->dev;resource_size_t mw_size; -int bar; +int bar, ret;if (pidx !=NTB_DEF_PEER_IDX ) {dev_err (dev,"Unsupported Peer ID %d\n" , pidx);@@ -509,19 +518,19 @@ static int ntb_epf_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx,writel (upper_32_bits (addr), ndev->ctrl_reg +NTB_EPF_UPPER_ADDR );writel (lower_32_bits (size), ndev->ctrl_reg +NTB_EPF_LOWER_SIZE );writel (upper_32_bits (size), ndev->ctrl_reg +NTB_EPF_UPPER_SIZE ); -ntb_epf_send_command_locked (ndev,CMD_CONFIGURE_MW , idx); + ret =ntb_epf_send_command_locked (ndev,CMD_CONFIGURE_MW , idx);mutex_unlock (&ndev->cmd_lock); -return 0 ; +return ret; }static int ntb_epf_mw_clear_trans (struct ntb_dev *ntb,int pidx,int idx)
drivers/ntb/hw/epf/ntb_hw_epf.c static int ntb_epf_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx, · line 535
Quoted 10 lines
drivers/ntb/hw/epf/ntb_hw_epf.c +2
-2{struct ntb_epf_dev *ndev =ntb_ndev (ntb);struct device *dev = ndev->dev; -int ret =0 ; +int ret; -ntb_epf_send_command (ndev,CMD_TEARDOWN_MW , idx); + ret =ntb_epf_send_command (ndev,CMD_TEARDOWN_MW , idx);if (ret)dev_err (dev,"Failed to teardown memory window\n" );
[Severity: Medium]
Can this error path in ntb_epf_mw_clear_trans() ever be reached?
In both endpoint handlers (drivers/pci/endpoint/functions/pci-epf-ntb.c and drivers/pci/endpoint/functions/pci-epf-vntb.c), epf_ntb_cmd_handler() handles COMMAND_TEARDOWN_MW by unconditionally setting:
case COMMAND_TEARDOWN_MW: epf_ntb_teardown_mw(ntb, ...); ctrl->command_status = COMMAND_STATUS_OK; break;
Because the endpoint never sets COMMAND_STATUS_ERROR for CMD_TEARDOWN_MW, the only way CMD_TEARDOWN_MW can fail is via a timeout (which sets ndev->cmd_status_unreliable = true) or by reading a late status after an earlier command already timed out (when ndev->cmd_status_unreliable is already true).
Since ntb_epf_send_command_locked() resets ret = 0 for CMD_TEARDOWN_MW whenever ndev->cmd_status_unreliable is true, won't ntb_epf_send_command() always return 0 here, leaving the dev_err() unreachable and silently masking teardown timeouts when callers such as ntb_free_mw(), perf_free_inbuf(), and tool_free_mw() are tearing down the window before freeing their DMA buffers?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Re: [PATCH v2 2/2] NTB: epf: propagate MW command errors while status is reliable
In reply to Avi WeissQuoted 8 lines · Avi Weiss
ntb_epf_mw_set_trans()andntb_epf_mw_clear_trans()discard command errors even when the endpoint explicitly rejects a request.A timed-out command may still complete on the endpoint. Propagating that timeout can cause callers to free DMA buffers before the mapping completes. A late completion may also be mistaken for the result of a subsequent command.
Quoted 7 lines
Record the first command timeout and propagate MW command errors only while completion status remains reliable. Preserve the original MW success-return behavior after a timeout to avoid releasing buffers based on ambiguous results.
Fixes 812ce2f8d14e ("NTB: Add support for EPF PCI Non-Transparent Bridge")Signed-off-by Avi Weiss <[email protected]>
LGTM. I think this is a nice improvement, and the points flagged by Sashiko seem to be pre-existing issues.
Thanks,
Koichiro
Quoted 74 lines
--- drivers/ntb/hw/epf/ntb_hw_epf.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-)drivers/ntb/hw/epf/ntb_hw_epf.c +14
-5@@ -102,6 +102,7 @@ struct ntb_epf_dev {struct device *dev;/* Mutex to protect providing commands to NTB EPF */ struct mutex cmd_lock; +bool cmd_status_unreliable;const enum pci_barno *barno_map;@@ -148,6 +149,7 @@ static int ntb_epf_send_command_locked(struct ntb_epf_dev *ndev, u32 command,break ;if (WARN_ON (timedout)) { + ndev->cmd_status_unreliable = true; ret = -ETIMEDOUT ;break ; }@@ -156,6 +158,13 @@ static int ntb_epf_send_command_locked(struct ntb_epf_dev *ndev, u32 command, }writew (0 , ndev->ctrl_reg +NTB_EPF_CMD_STATUS ); +/* +* After a timeout, a status may refer to an earlier command. +* MW callers may free buffers on error, so retain old semantics. +*/ +if (ndev->cmd_status_unreliable && + (command ==CMD_CONFIGURE_MW || command ==CMD_TEARDOWN_MW )) + ret =0 ;return ret; }@@ -485,7 +494,7 @@ static int ntb_epf_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx,struct ntb_epf_dev *ndev =ntb_ndev (ntb);struct device *dev = ndev->dev;resource_size_t mw_size; -int bar; +int bar, ret;if (pidx !=NTB_DEF_PEER_IDX ) {dev_err (dev,"Unsupported Peer ID %d\n" , pidx);@@ -509,19 +518,19 @@ static int ntb_epf_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx,writel (upper_32_bits (addr), ndev->ctrl_reg +NTB_EPF_UPPER_ADDR );writel (lower_32_bits (size), ndev->ctrl_reg +NTB_EPF_LOWER_SIZE );writel (upper_32_bits (size), ndev->ctrl_reg +NTB_EPF_UPPER_SIZE ); -ntb_epf_send_command_locked (ndev,CMD_CONFIGURE_MW , idx); + ret =ntb_epf_send_command_locked (ndev,CMD_CONFIGURE_MW , idx);mutex_unlock (&ndev->cmd_lock); -return 0 ; +return ret; }static int ntb_epf_mw_clear_trans (struct ntb_dev *ntb,int pidx,int idx) {struct ntb_epf_dev *ndev =ntb_ndev (ntb);struct device *dev = ndev->dev; -int ret =0 ; +int ret; -ntb_epf_send_command (ndev,CMD_TEARDOWN_MW , idx); + ret =ntb_epf_send_command (ndev,CMD_TEARDOWN_MW , idx);if (ret)dev_err (dev,"Failed to teardown memory window\n" );Signature · 1 line
2.43.0