* [PATCH net v4 0/3] Fix to possible skb leak due to race condtion in tx path
@ 2026-07-21 2:20 Selvamani Rajagopal via B4 Relay
2026-07-21 2:20 ` [PATCH net v4 1/3] net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances Selvamani Rajagopal via B4 Relay
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-07-21 2:20 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, Piergiorgio Beruto,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Andrew Lunn, Parthiban Veerasooran,
Selvamani Rajagopal
Now the traffic is handled in threaded IRQ, and the
disable_traffic flag is checked before handling the
data, new race condition is exposed, in which
buffer may leak, if threaded IRQ interrupts the
trasmit path midway.
With this change, disable_traffic and waiting_tx_skb
pointer are protected by spin lock/unlock pair.
This is highlighted in Sashiko review
https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260611-level-trigger-v5-0-4533a9e85ce2%40onsemi.com
Also on buffer overrun condition, probably due to loss of
SPI data chunks, receive path doesn't see the expected
data chunk with end_valid bit set. As a result, driver
keeps adding data chunks to the skb before running out
of space and kernel panic is seen.
With this change, before adding data to the skb, if there
is no space, skb is freed and driver starts looking for
new frame by looking for a data chunk with start_valid
bit set.
[ 705.405490] skbuff: skb_over_panic: text:ffffffd2eb72a264 len:1600 put:64 head:ffffff804e5cdc40 data:ffffff804e5cdc80 tail:0x680 end:0x640 dev:eth1
[ 705.405569] ------------[ cut here ]------------
[ 705.405575] kernel BUG at net/core/skbuff.c:214!
[ 705.405589] Internal error: Oops - BUG: 00000000f2000800 [#1] SMP
[ 6703.427690] Call trace:
[ 705.925157] skb_panic+0x58/0x68 (P)
[ 705.928726] skb_put+0x74/0x80
[ 705.931772] oa_tc6_update_rx_skb+0x44/0x98 [oa_tc6_mod]
[ 705.937084] oa_tc6_macphy_threaded_irq+0x3f4/0x900 [oa_tc6_mod]
[ 705.943084] irq_thread_fn+0x34/0xb8
[ 705.946654] irq_thread+0x1a0/0x300
[ 705.950134] kthread+0x138/0x150
[ 705.953356] ret_from_fork+0x10/0x20
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
Changes in v4:
- As disable_traffic means device is uselss unless
driver re-loaded, all tx queues are turned off.
- Process all the received chunks on buffer overflow,
as long as data chunks doesn't have any error bits set
in their footer.
- Added spin lock protection in every place wait_tx_skb
is used.
- Carrier is not turned off on disable_traffic.
- Link to v3: https://lore.kernel.org/r/20260705-fix-race-condition-and-crash-v3-0-3e51841e4d08@onsemi.com
Changes in v3:
- Cover all the instances of disable_traffic flag with
spin lock to serialize the access
- Disabling the tx queue and mark the carrier off when
disable_traffic is set.
- Continue processing received chunks on buffer overflow
error and "out of skb" error.
- Link to v2: https://lore.kernel.org/r/20260626-fix-race-condition-and-crash-v2-0-b6c5c10e604f@onsemi.com
Changes in v2:
- Improvment to how error -EAGAIN is handled. Took care of
couple of use cases where start_bit and end_bit may be missing or
repeated due to lost data chunks.
- Protected handling of waiting_tx_skb pointer with spin lock
- Link to v1: https://lore.kernel.org/r/20260621-fix-race-condition-and-crash-v1-0-87e290d9357f@onsemi.com
---
Selvamani Rajagopal (3):
net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances
net: ethernet: oa_tc6: Improvements to error recovery
net: ethernet: oa_tc6: Disabled tx queues when disable_traffic is set
drivers/net/ethernet/oa_tc6.c | 236 +++++++++++++++++++++++++++++++-----------
1 file changed, 175 insertions(+), 61 deletions(-)
---
base-commit: 1c975de3343cdef506f2eecc833cc1f14b0401c4
change-id: 20260621-fix-race-condition-and-crash-94d055a665c4
Best regards,
--
Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net v4 1/3] net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances
2026-07-21 2:20 [PATCH net v4 0/3] Fix to possible skb leak due to race condtion in tx path Selvamani Rajagopal via B4 Relay
@ 2026-07-21 2:20 ` Selvamani Rajagopal via B4 Relay
2026-07-22 10:08 ` Parthiban Veerasooran
2026-07-21 2:20 ` [PATCH net v4 2/3] net: ethernet: oa_tc6: Improvements to error recovery Selvamani Rajagopal via B4 Relay
2026-07-21 2:20 ` [PATCH net v4 3/3] net: ethernet: oa_tc6: Disabled tx queues when disable_traffic is set Selvamani Rajagopal via B4 Relay
2 siblings, 1 reply; 7+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-07-21 2:20 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, Piergiorgio Beruto,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Andrew Lunn, Parthiban Veerasooran,
Selvamani Rajagopal
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Threaded IRQ uses waiting_tx_skb. Transmit path also uses
this pointer without any mutual exclusion protection. As a
result, it might leak skb buffer, particularly threaded IRQ
runs in the middle of tranmsmit path, near skb_linearize.
Fixes: b542d13fab0f ("net: ethernet: oa_tc6: Interrupt is active low, level triggered.")
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v4
- No change
changes in v3
- Added the missed out spin lock protection for
waiting_tx_skb and disable_traffic flag
changes in v2
- added the missing prefix to the title
---
drivers/net/ethernet/oa_tc6.c | 100 +++++++++++++++++++++++++++++-------------
1 file changed, 70 insertions(+), 30 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 0727d53345a3..5b24cce4f9b5 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -652,6 +652,26 @@ static int oa_tc6_enable_data_transfer(struct oa_tc6 *tc6)
return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, value);
}
+/* Called when a frame that is meant to be transmitted, is dropped. */
+static void oa_tc6_drop_tx_skb(struct oa_tc6 *tc6, struct sk_buff *skb)
+{
+ if (skb) {
+ tc6->netdev->stats.tx_dropped++;
+ dev_kfree_skb_any(skb);
+ }
+}
+
+static struct sk_buff *oa_tc6_detach_waiting_tx_skb(struct oa_tc6 *tc6)
+{
+ struct sk_buff *skb;
+
+ lockdep_assert_held(&tc6->tx_skb_lock);
+ skb = tc6->waiting_tx_skb;
+ tc6->waiting_tx_skb = NULL;
+
+ return skb;
+}
+
static void oa_tc6_cleanup_ongoing_rx_skb(struct oa_tc6 *tc6)
{
if (tc6->rx_skb) {
@@ -663,26 +683,30 @@ static void oa_tc6_cleanup_ongoing_rx_skb(struct oa_tc6 *tc6)
static void oa_tc6_cleanup_ongoing_tx_skb(struct oa_tc6 *tc6)
{
- if (tc6->ongoing_tx_skb) {
- tc6->netdev->stats.tx_dropped++;
- kfree_skb(tc6->ongoing_tx_skb);
- tc6->ongoing_tx_skb = NULL;
- }
+ oa_tc6_drop_tx_skb(tc6, tc6->ongoing_tx_skb);
+ tc6->ongoing_tx_skb = NULL;
}
static void oa_tc6_cleanup_waiting_tx_skb(struct oa_tc6 *tc6)
{
- if (tc6->waiting_tx_skb) {
- tc6->netdev->stats.tx_dropped++;
- kfree_skb(tc6->waiting_tx_skb);
- tc6->waiting_tx_skb = NULL;
- }
+ struct sk_buff *skb;
+
+ spin_lock_bh(&tc6->tx_skb_lock);
+ skb = oa_tc6_detach_waiting_tx_skb(tc6);
+ spin_unlock_bh(&tc6->tx_skb_lock);
+
+ oa_tc6_drop_tx_skb(tc6, skb);
}
-static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
+static void oa_tc6_free_ongoing_skbs(struct oa_tc6 *tc6)
{
oa_tc6_cleanup_ongoing_tx_skb(tc6);
oa_tc6_cleanup_ongoing_rx_skb(tc6);
+}
+
+static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
+{
+ oa_tc6_free_ongoing_skbs(tc6);
oa_tc6_cleanup_waiting_tx_skb(tc6);
}
@@ -693,9 +717,15 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
static void oa_tc6_disable_traffic(struct oa_tc6 *tc6)
{
u32 regval = INT_MASK0_ALL_INTERRUPTS;
+ struct sk_buff *skb;
+ spin_lock_bh(&tc6->tx_skb_lock);
tc6->disable_traffic = true;
- oa_tc6_free_pending_skbs(tc6);
+ skb = oa_tc6_detach_waiting_tx_skb(tc6);
+ spin_unlock_bh(&tc6->tx_skb_lock);
+
+ oa_tc6_drop_tx_skb(tc6, skb);
+ oa_tc6_free_ongoing_skbs(tc6);
oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval);
oa_tc6_read_register(tc6, OA_TC6_REG_STATUS0, ®val);
oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, regval);
@@ -1136,8 +1166,7 @@ static int oa_tc6_try_spi_transfer(struct oa_tc6 *tc6)
if (ret == -EAGAIN)
continue;
- oa_tc6_cleanup_ongoing_tx_skb(tc6);
- oa_tc6_cleanup_ongoing_rx_skb(tc6);
+ oa_tc6_free_ongoing_skbs(tc6);
netdev_err(tc6->netdev, "Device error: %d\n", ret);
return ret;
}
@@ -1159,15 +1188,20 @@ static irqreturn_t oa_tc6_macphy_threaded_irq(int irq, void *data)
* no need to attempt spi transfer, once it fails. Pending skbs
* are already freed.
*/
- if (!tc6->disable_traffic) {
- while (tc6->int_flag ||
- (tc6->waiting_tx_skb && tc6->tx_credits)) {
- ret = oa_tc6_try_spi_transfer(tc6);
- if (ret) {
- disable_irq_nosync(tc6->spi->irq);
- oa_tc6_disable_traffic(tc6);
- break;
- }
+ spin_lock_bh(&tc6->tx_skb_lock);
+ if (tc6->disable_traffic) {
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ return IRQ_HANDLED;
+ }
+ spin_unlock_bh(&tc6->tx_skb_lock);
+
+ while (tc6->int_flag ||
+ (tc6->waiting_tx_skb && tc6->tx_credits)) {
+ ret = oa_tc6_try_spi_transfer(tc6);
+ if (ret) {
+ disable_irq_nosync(tc6->spi->irq);
+ oa_tc6_disable_traffic(tc6);
+ break;
}
}
@@ -1250,18 +1284,22 @@ EXPORT_SYMBOL_GPL(oa_tc6_zero_align_receive_frame_enable);
*/
netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb)
{
- if (tc6->disable_traffic || tc6->waiting_tx_skb) {
- netif_stop_queue(tc6->netdev);
- return NETDEV_TX_BUSY;
- }
-
if (skb_linearize(skb)) {
- dev_kfree_skb_any(skb);
- tc6->netdev->stats.tx_dropped++;
+ oa_tc6_drop_tx_skb(tc6, skb);
return NETDEV_TX_OK;
}
spin_lock_bh(&tc6->tx_skb_lock);
+ if (tc6->waiting_tx_skb) {
+ netif_stop_queue(tc6->netdev);
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ return NETDEV_TX_BUSY;
+ }
+ if (tc6->disable_traffic) {
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ oa_tc6_drop_tx_skb(tc6, skb);
+ return NETDEV_TX_OK;
+ }
tc6->waiting_tx_skb = skb;
spin_unlock_bh(&tc6->tx_skb_lock);
@@ -1393,7 +1431,9 @@ EXPORT_SYMBOL_GPL(oa_tc6_init);
*/
void oa_tc6_exit(struct oa_tc6 *tc6)
{
+ spin_lock_bh(&tc6->tx_skb_lock);
tc6->disable_traffic = true;
+ spin_unlock_bh(&tc6->tx_skb_lock);
disable_irq(tc6->spi->irq);
oa_tc6_phy_exit(tc6);
oa_tc6_free_pending_skbs(tc6);
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH net v4 2/3] net: ethernet: oa_tc6: Improvements to error recovery
2026-07-21 2:20 [PATCH net v4 0/3] Fix to possible skb leak due to race condtion in tx path Selvamani Rajagopal via B4 Relay
2026-07-21 2:20 ` [PATCH net v4 1/3] net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances Selvamani Rajagopal via B4 Relay
@ 2026-07-21 2:20 ` Selvamani Rajagopal via B4 Relay
2026-07-22 10:09 ` Parthiban Veerasooran
2026-07-21 2:20 ` [PATCH net v4 3/3] net: ethernet: oa_tc6: Disabled tx queues when disable_traffic is set Selvamani Rajagopal via B4 Relay
2 siblings, 1 reply; 7+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-07-21 2:20 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, Piergiorgio Beruto,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Andrew Lunn, Parthiban Veerasooran,
Selvamani Rajagopal
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
When oversubscribed traffic causes lot of buffer overflow errors,
probably due to loss of data chunks, driver fails to find a
data chunk with end_valid bit set, before it runs out of sk buffer
space. As a result, assert is seen during skb_put.
Now check is made if tail + len > end, driver abandons the current
data and starts look for a data chunk with start_valid bit,
that is a new frame.
SK buffer allocation error is considered as recoverable error.
Fixes: d70a0d8f2f2d ("net: ethernet: oa_tc6: implement receive path to receive rx ethernet frames")
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v4
- rx_buf_overflow flag cleared, when end of frame and start of
frame are handled in the same data chunk.
- Added more comments to answer some of the AI review questions.
changes in v3
- Continue processing more chunks on error code -EAGAIN. Previously
we were bailing out.
changes in v2
- Check rx_skb pointer before new allocation and NULL before use.
---
drivers/net/ethernet/oa_tc6.c | 131 ++++++++++++++++++++++++++++++++----------
1 file changed, 100 insertions(+), 31 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 5b24cce4f9b5..9e6850ccfe6c 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -710,6 +710,12 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
oa_tc6_cleanup_waiting_tx_skb(tc6);
}
+static void oa_tc6_look_for_new_frame(struct oa_tc6 *tc6)
+{
+ tc6->rx_buf_overflow = true;
+ oa_tc6_cleanup_ongoing_rx_skb(tc6);
+}
+
/* If the failure is at SPI interface level, masking and clearing
* the interrupt of the device won't work. Since SPI interrupt is
* disabled, it should stop the repeated interrupts.
@@ -753,8 +759,7 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
}
if (FIELD_GET(STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) {
- tc6->rx_buf_overflow = true;
- oa_tc6_cleanup_ongoing_rx_skb(tc6);
+ oa_tc6_look_for_new_frame(tc6);
net_err_ratelimited("%s: Receive buffer overflow error\n",
tc6->netdev->name);
return -EAGAIN;
@@ -780,6 +785,8 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer)
{
+ int ret = 0;
+
/* Process rx chunk footer for the following,
* 1. tx credits
* 2. errors if any from MAC-PHY
@@ -790,9 +797,11 @@ static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer)
footer);
if (FIELD_GET(OA_TC6_DATA_FOOTER_EXTENDED_STS, footer)) {
- int ret = oa_tc6_process_extended_status(tc6);
-
- if (ret)
+ ret = oa_tc6_process_extended_status(tc6);
+ /* EAGAIN error is recoverable. Move on to check
+ * HEADER and SYNC errors before returning.
+ */
+ if (ret && ret != -EAGAIN)
return ret;
}
@@ -810,7 +819,7 @@ static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer)
return -ENODEV;
}
- return 0;
+ return ret;
}
static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6)
@@ -835,13 +844,35 @@ static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6)
tc6->rx_skb = NULL;
}
-static void oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length)
+/* On oversubscribed traffic condition, particularly with overwhelming rx
+ * buffer overflow errors, there could be data chunk loss. If tail + length
+ * goes beyond end pointer, that is an indication that the data chunk with
+ * end_valid bit is lost. Time to look for a data chunk with start_valid bit.
+ *
+ * If rx_skb is NULL, it is time to start looking for data chunk with
+ * start_bit.
+ */
+static int oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length)
{
+ if (!tc6->rx_skb ||
+ (tc6->rx_skb->tail + length) > tc6->rx_skb->end) {
+ oa_tc6_look_for_new_frame(tc6);
+ return -EAGAIN;
+ }
+
memcpy(skb_put(tc6->rx_skb, length), payload, length);
+ return 0;
}
+/* On overwhelming rx buffer overflow errors, due to data chunk loss, it is
+ * possible that we get two data chunks with start_valid bit set, without
+ * end_valid bit set in between. In this case, rx_skb would have a valid
+ * buffer pointer. We should release, if a valid pointer is found before
+ * allocating a new one.
+ */
static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6)
{
+ oa_tc6_cleanup_ongoing_rx_skb(tc6);
tc6->rx_skb = netdev_alloc_skb_ip_align(tc6->netdev, tc6->netdev->mtu +
ETH_HLEN + ETH_FCS_LEN);
if (!tc6->rx_skb) {
@@ -861,7 +892,9 @@ static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload,
if (ret)
return ret;
- oa_tc6_update_rx_skb(tc6, payload, size);
+ ret = oa_tc6_update_rx_skb(tc6, payload, size);
+ if (ret)
+ return ret;
oa_tc6_submit_rx_skb(tc6);
@@ -876,22 +909,24 @@ static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size)
if (ret)
return ret;
- oa_tc6_update_rx_skb(tc6, payload, size);
-
- return 0;
+ return oa_tc6_update_rx_skb(tc6, payload, size);
}
-static void oa_tc6_prcs_rx_frame_end(struct oa_tc6 *tc6, u8 *payload, u16 size)
+static int oa_tc6_prcs_rx_frame_end(struct oa_tc6 *tc6, u8 *payload, u16 size)
{
- oa_tc6_update_rx_skb(tc6, payload, size);
+ int ret;
- oa_tc6_submit_rx_skb(tc6);
+ ret = oa_tc6_update_rx_skb(tc6, payload, size);
+ if (!ret)
+ oa_tc6_submit_rx_skb(tc6);
+ return ret;
}
-static void oa_tc6_prcs_ongoing_rx_frame(struct oa_tc6 *tc6, u8 *payload,
- u32 footer)
+static int oa_tc6_prcs_ongoing_rx_frame(struct oa_tc6 *tc6, u8 *payload,
+ u32 footer)
{
- oa_tc6_update_rx_skb(tc6, payload, OA_TC6_CHUNK_PAYLOAD_SIZE);
+ return oa_tc6_update_rx_skb(tc6, payload,
+ OA_TC6_CHUNK_PAYLOAD_SIZE);
}
static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
@@ -931,8 +966,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
/* Process the chunk with only rx frame end */
if (end_valid && !start_valid) {
size = end_byte_offset + 1;
- oa_tc6_prcs_rx_frame_end(tc6, data, size);
- return 0;
+ return oa_tc6_prcs_rx_frame_end(tc6, data, size);
}
/* Process the chunk with previous rx frame end and next rx frame
@@ -946,6 +980,14 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
if (tc6->rx_skb) {
size = end_byte_offset + 1;
oa_tc6_prcs_rx_frame_end(tc6, data, size);
+
+ /* Purpose of rx_buf_overflow is make the
+ * code to look for new frame. At this
+ * stage, we have a new frame to process.
+ * So, making it false, in case it is set
+ * to true by oa_tc6_prcs_rx_frame_end.
+ */
+ tc6->rx_buf_overflow = false;
}
size = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset;
return oa_tc6_prcs_rx_frame_start(tc6,
@@ -954,9 +996,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
}
/* Process the chunk with ongoing rx frame data */
- oa_tc6_prcs_ongoing_rx_frame(tc6, data, footer);
-
- return 0;
+ return oa_tc6_prcs_ongoing_rx_frame(tc6, data, footer);
}
static u32 oa_tc6_get_rx_chunk_footer(struct oa_tc6 *tc6, u16 footer_offset)
@@ -972,8 +1012,9 @@ static u32 oa_tc6_get_rx_chunk_footer(struct oa_tc6 *tc6, u16 footer_offset)
static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length)
{
u16 no_of_rx_chunks = length / OA_TC6_CHUNK_SIZE;
+ bool retry = false;
+ int ret = 0;
u32 footer;
- int ret;
/* All the rx chunks in the receive SPI data buffer are examined here */
for (int i = 0; i < no_of_rx_chunks; i++) {
@@ -982,8 +1023,11 @@ static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length)
OA_TC6_CHUNK_PAYLOAD_SIZE);
ret = oa_tc6_process_rx_chunk_footer(tc6, footer);
- if (ret)
- return ret;
+ if (ret) {
+ if (ret != -EAGAIN)
+ return ret;
+ retry = true;
+ }
/* If there is a data valid chunks then process it for the
* information needed to determine the validity and the location
@@ -995,12 +1039,35 @@ static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length)
ret = oa_tc6_prcs_rx_chunk_payload(tc6, payload,
footer);
- if (ret)
- return ret;
+ if (ret) {
+ if (ret != -ENOMEM && ret != -EAGAIN)
+ return ret;
+ retry = true;
+ }
}
}
- return 0;
+ /* Not bailing out on recoverable error codes, -EAGAIN and
+ * -ENOMEM. If subsequent loop iterations, if any, succeeds,
+ * error code would be overwritten. retry flag helps to
+ * make the caller to continue and retry. Since recovery
+ * action for -ENOMEM and -EAGAIN are same, we are returning
+ * one of the error codes, that is -EAGAIN.
+ *
+ * Successful recovery depends on how small the frames are,
+ * how many chunks, among the received chunks triggered the
+ * error, whether data is intact even with error conditions.
+ * As a result, there is no single, best method to recover
+ * most data when error conditions hit. We do our best by
+ * processing all the chunks with good "footer header" and
+ * "data valid" bit set.
+ */
+ if (retry) {
+ ret = -EAGAIN;
+ oa_tc6_look_for_new_frame(tc6);
+ }
+
+ return ret;
}
static __be32 oa_tc6_prepare_data_header(bool data_valid, bool start_valid,
@@ -1162,10 +1229,12 @@ static int oa_tc6_try_spi_transfer(struct oa_tc6 *tc6)
}
ret = oa_tc6_process_spi_data_rx_buf(tc6, spi_len);
- if (ret) {
- if (ret == -EAGAIN)
- continue;
+ /* Not continuing with the next iteration to give
+ * waiting_tx_skb a chance to get drained, if
+ * needed.
+ */
+ if (ret && ret != -EAGAIN) {
oa_tc6_free_ongoing_skbs(tc6);
netdev_err(tc6->netdev, "Device error: %d\n", ret);
return ret;
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH net v4 3/3] net: ethernet: oa_tc6: Disabled tx queues when disable_traffic is set
2026-07-21 2:20 [PATCH net v4 0/3] Fix to possible skb leak due to race condtion in tx path Selvamani Rajagopal via B4 Relay
2026-07-21 2:20 ` [PATCH net v4 1/3] net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances Selvamani Rajagopal via B4 Relay
2026-07-21 2:20 ` [PATCH net v4 2/3] net: ethernet: oa_tc6: Improvements to error recovery Selvamani Rajagopal via B4 Relay
@ 2026-07-21 2:20 ` Selvamani Rajagopal via B4 Relay
2026-07-22 10:09 ` Parthiban Veerasooran
2 siblings, 1 reply; 7+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-07-21 2:20 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, Piergiorgio Beruto,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Andrew Lunn, Parthiban Veerasooran,
Selvamani Rajagopal
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Previously, TX queue interface was stopped when
disable_traffic flag was set. It is more appropriate to
disable the queue as there is no recovery, once
disable_traffic is set. Carrier is also marked off
Fixes: b542d13fab0f ("net: ethernet: oa_tc6: Interrupt is active low, level triggered.")
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
changes in v4
- Reverted the the statement that turned carrier off on
disble_traffic, as it may have side effects
changes in v3
- New patch. Carrier marked off once disable_traffic is set
---
drivers/net/ethernet/oa_tc6.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 9e6850ccfe6c..4b8f6280be51 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -730,6 +730,11 @@ static void oa_tc6_disable_traffic(struct oa_tc6 *tc6)
skb = oa_tc6_detach_waiting_tx_skb(tc6);
spin_unlock_bh(&tc6->tx_skb_lock);
+ /* disable_traffic, when set, is a point of no
+ * return to working state. TX queues are
+ * disabled.
+ */
+ netif_tx_disable(tc6->netdev);
oa_tc6_drop_tx_skb(tc6, skb);
oa_tc6_free_ongoing_skbs(tc6);
oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval);
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH net v4 1/3] net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances
2026-07-21 2:20 ` [PATCH net v4 1/3] net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances Selvamani Rajagopal via B4 Relay
@ 2026-07-22 10:08 ` Parthiban Veerasooran
0 siblings, 0 replies; 7+ messages in thread
From: Parthiban Veerasooran @ 2026-07-22 10:08 UTC (permalink / raw)
To: Selvamani.Rajagopal
Cc: netdev, linux-kernel, Andrew Lunn, Andrew Lunn,
Piergiorgio Beruto, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Hi Selvamani,
On 21/07/26 7:50 am, Selvamani Rajagopal via B4 Relay wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>
> From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
>
> Threaded IRQ uses waiting_tx_skb. Transmit path also uses
> this pointer without any mutual exclusion protection. As a
> result, it might leak skb buffer, particularly threaded IRQ
> runs in the middle of tranmsmit path, near skb_linearize.
Typo: "tranmsmit" -> "transmit"
>
> Fixes: b542d13fab0f ("net: ethernet: oa_tc6: Interrupt is active low, level triggered.")
> Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
>
> ---
> changes in v4
> - No change
> changes in v3
> @@ -1393,7 +1431,9 @@ EXPORT_SYMBOL_GPL(oa_tc6_init);
> */
> void oa_tc6_exit(struct oa_tc6 *tc6)
> {
> + spin_lock_bh(&tc6->tx_skb_lock);
> tc6->disable_traffic = true;
I don't know whether this is the right place to provide this comment,
but FYI: the disable_traffic read/check in the hard-IRQ handler
(oa_tc6_macphy_isr()) is not protected, while it may be written in
oa_tc6_exit() under tx_skb_lock.
Of course, spin_lock_bh() cannot be used in hard-IRQ context, so perhaps
a different synchronization approach is needed?
Best regards,
Parthiban V
> + spin_unlock_bh(&tc6->tx_skb_lock);
> disable_irq(tc6->spi->irq);
> oa_tc6_phy_exit(tc6);
> oa_tc6_free_pending_skbs(tc6);
>
> --
> 2.43.0
>
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net v4 2/3] net: ethernet: oa_tc6: Improvements to error recovery
2026-07-21 2:20 ` [PATCH net v4 2/3] net: ethernet: oa_tc6: Improvements to error recovery Selvamani Rajagopal via B4 Relay
@ 2026-07-22 10:09 ` Parthiban Veerasooran
0 siblings, 0 replies; 7+ messages in thread
From: Parthiban Veerasooran @ 2026-07-22 10:09 UTC (permalink / raw)
To: Selvamani.Rajagopal
Cc: netdev, linux-kernel, Andrew Lunn, Andrew Lunn,
Piergiorgio Beruto, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Imperative mood for the subject: "Improve error recovery" (netdev
convention).
On 21/07/26 7:50 am, Selvamani Rajagopal via B4 Relay wrote:
> static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
> @@ -931,8 +966,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
> /* Process the chunk with only rx frame end */
> if (end_valid && !start_valid) {
> size = end_byte_offset + 1;
> - oa_tc6_prcs_rx_frame_end(tc6, data, size);
> - return 0;
> + return oa_tc6_prcs_rx_frame_end(tc6, data, size);
> }
>
> /* Process the chunk with previous rx frame end and next rx frame
> @@ -946,6 +980,14 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
> if (tc6->rx_skb) {
> size = end_byte_offset + 1;
> oa_tc6_prcs_rx_frame_end(tc6, data, size);
oa_tc6_prcs_rx_frame_end() now returns int, but this drops the return —
every other call in this patch propagates it. Is it deliberate?
> +
> + /* Purpose of rx_buf_overflow is make the
> + * code to look for new frame. At this
> + * stage, we have a new frame to process.
> + * So, making it false, in case it is set
> + * to true by oa_tc6_prcs_rx_frame_end.
> + */
> + tc6->rx_buf_overflow = false;
> }
> size = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset;
> return oa_tc6_prcs_rx_frame_start(tc6,
> @@ -954,9 +996,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
> }
>
> /* Process the chunk with ongoing rx frame data */
> - oa_tc6_prcs_ongoing_rx_frame(tc6, data, footer);
> -
> - return 0;
> + return oa_tc6_prcs_ongoing_rx_frame(tc6, data, footer);
footer is unused here. Either drop the unused parameter or a note why it
stays.
Best regards,
Parthiban V
> }
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net v4 3/3] net: ethernet: oa_tc6: Disabled tx queues when disable_traffic is set
2026-07-21 2:20 ` [PATCH net v4 3/3] net: ethernet: oa_tc6: Disabled tx queues when disable_traffic is set Selvamani Rajagopal via B4 Relay
@ 2026-07-22 10:09 ` Parthiban Veerasooran
0 siblings, 0 replies; 7+ messages in thread
From: Parthiban Veerasooran @ 2026-07-22 10:09 UTC (permalink / raw)
To: Selvamani.Rajagopal
Cc: netdev, linux-kernel, Andrew Lunn, Andrew Lunn,
Piergiorgio Beruto, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Imperative mood for the subject: "Disable tx queues when disable_traffic
is set" (netdev convention).
On 21/07/26 7:50 am, Selvamani Rajagopal via B4 Relay wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>
> From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
>
> Previously, TX queue interface was stopped when
> disable_traffic flag was set. It is more appropriate to
> disable the queue as there is no recovery, once
> disable_traffic is set. Carrier is also marked off
"Carrier is also marked off" contradicts both the code and your own v4
changelog: the patch only adds netif_tx_disable(), there is no
netif_carrier_off(), and v4 says you reverted the carrier change. Please
drop this sentence — the message currently describes behavior the patch
doesn't implement.
>
> Fixes: b542d13fab0f ("net: ethernet: oa_tc6: Interrupt is active low, level triggered.")
> Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
>
--- is missing
> changes in v4
> - Reverted the the statement that turned carrier off on
typo.
> disble_traffic, as it may have side effects
typo.
> changes in v3
> - New patch. Carrier marked off once disable_traffic is set
> ---
> drivers/net/ethernet/oa_tc6.c | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
> index 9e6850ccfe6c..4b8f6280be51 100644
> --- a/drivers/net/ethernet/oa_tc6.c
> +++ b/drivers/net/ethernet/oa_tc6.c
> @@ -730,6 +730,11 @@ static void oa_tc6_disable_traffic(struct oa_tc6 *tc6)
> skb = oa_tc6_detach_waiting_tx_skb(tc6);
> spin_unlock_bh(&tc6->tx_skb_lock);
>
> + /* disable_traffic, when set, is a point of no
> + * return to working state. TX queues are
> + * disabled.
> + */
Comment wraps at ~40 columns; fill to ~80 like the rest of the file.
Best regards,
Parthiban V
> + netif_tx_disable(tc6->netdev);
> oa_tc6_drop_tx_skb(tc6, skb);
> oa_tc6_free_ongoing_skbs(tc6);
> oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval);
>
> --
> 2.43.0
>
>
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-07-22 10:09 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-21 2:20 [PATCH net v4 0/3] Fix to possible skb leak due to race condtion in tx path Selvamani Rajagopal via B4 Relay
2026-07-21 2:20 ` [PATCH net v4 1/3] net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances Selvamani Rajagopal via B4 Relay
2026-07-22 10:08 ` Parthiban Veerasooran
2026-07-21 2:20 ` [PATCH net v4 2/3] net: ethernet: oa_tc6: Improvements to error recovery Selvamani Rajagopal via B4 Relay
2026-07-22 10:09 ` Parthiban Veerasooran
2026-07-21 2:20 ` [PATCH net v4 3/3] net: ethernet: oa_tc6: Disabled tx queues when disable_traffic is set Selvamani Rajagopal via B4 Relay
2026-07-22 10:09 ` Parthiban Veerasooran
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox