[PATCH 4/5] net: designware: eqos: fix NULL pointer dereference on LLDP packets
Oleksij Rempel
o.rempel at pengutronix.de
Fri Aug 11 01:40:27 PDT 2023
If promisc mode is enabled (which is enabled for DSA switches by default) a
LLDP frame received by barebox will trigger following panic:
DABT (current EL) exception (ESR 0x9600014b) at 0x0000000000000000
elr: 00000000bfd967d8 lr : 00000000bfd963e0
x0 : 0000000000000000 x1 : 00000000000000e9
x2 : 0000000000000040 x3 : 000000000000003f
x4 : 0000000000000000 x5 : 00000000000001a0
x6 : 0000000000000005 x7 : 0000000030200000
x8 : 000000007feda6a8 x9 : 0000000000000000
x10: 0000000000000000 x11: 00000000fffffffa
x12: 00000000fffffffa x13: 0000000000000020
x14: 0000000000000000 x15: 0000000000000001
x16: 00000000bfff74c8 x17: 0000000000000001
x18: 00000000bfff7a60 x19: 000000007fee20c0
x20: 0000000000000000 x21: 000000007fee0dc8
x22: 000000007fee0dc8 x23: 00000000000000ea
x24: 0000000000000080 x25: 0000000000000000
x26: 00000000000000ea x27: 000000007fee2040
x28: 0000000000000000 x29: 00000000bfff7a60
Call trace:
[<bfd967d8>] (v8_inv_dcache_range+0x1c/0x34) from [<bfd953fc>] (arch_sync_dma_for_cpu+0x14/0x20)
[<bfd953fc>] (arch_sync_dma_for_cpu+0x14/0x20) from [<bfd25a64>] (eqos_recv+0x78/0x104)
[<bfd25a64>] (eqos_recv+0x78/0x104) from [<bfd80724>] (eth_rx+0xd0/0x160)
[<bfd80724>] (eth_rx+0xd0/0x160) from [<bfd80b84>] (net_poll+0x24/0x34)
[<bfd80b84>] (net_poll+0x24/0x34) from [<bfd80bbc>] (__net_poll+0x28/0x3c)
[<bfd80bbc>] (__net_poll+0x28/0x3c) from [<bfd0ec7c>] (poller_call+0x58/0x68)
[<bfd0ec7c>] (poller_call+0x58/0x68) from [<bfd0ea98>] (resched+0x38/0x48)
[<bfd0ea98>] (resched+0x38/0x48) from [<bfd77be0>] (readline+0xb4/0x89c)
[<bfd77be0>] (readline+0xb4/0x89c) from [<bfd0f17c>] (file_get+0x94/0x1d8)
[<bfd0f17c>] (file_get+0x94/0x1d8) from [<bfd0f92c>] (parse_stream.constprop.0+0x40/0x534)
[<bfd0f92c>] (parse_stream.constprop.0+0x40/0x534) from [<bfd0ff10>] (parse_stream_outer+0xf0/0x1ec)
[<bfd0ff10>] (parse_stream_outer+0xf0/0x1ec) from [<bfd10e0c>] (run_shell+0x60/0x98)
[<bfd10e0c>] (run_shell+0x60/0x98) from [<bfd01854>] (run_init+0x170/0x2b0)
[<bfd01854>] (run_init+0x170/0x2b0) from [<bfd019e4>] (start_barebox+0x50/0x8c)
[<bfd019e4>] (start_barebox+0x50/0x8c) from [<bfd95794>] (barebox_non_pbl_start+0x11c/0x150)
[<bfd95794>] (barebox_non_pbl_start+0x11c/0x150) from [<bfd0000c>] (__bare_init_start+0x0/0x4)
[<bfd0000c>] (__bare_init_start+0x0/0x4) from [<402041fc>] (0x402041fc)
[<402041fc>] (0x402041fc) from [<40203bac>] (0x40203bac)
panic: unhandled exception
This issue can be reproduced by using following command and link partner:
mausezahn enp1s0f1 01:80:c2:00:00:0e:08:60:6e:1f:a3:9c:88:cc
The problem caused this issue is wrong or may be different (depending on the HW)
interpretation of DMA RX buffers. The DMA descriptors have distinct formats
depending on their direction: Read Format for CPU-to-DMA and Write-Back Format
for DMA-to-CPU. Previously, the driver did not distinguish between these,
leading to misinterpretations of the descriptor fields. For example the
driver expected a DMA buffer pointer in the Write-Back desc0 which is
actually a VLAN tag descriptor and contains artifacts of DMA buffer
pointer only by accident. To fix it we should store DMA buffer pointers
in a separate array and use them. To prevent more of misunderstandings,
i renamed variables to make it visible what format of DMA buffer we
actually using.
Signed-off-by: Oleksij Rempel <o.rempel at pengutronix.de>
---
drivers/net/designware_eqos.c | 55 ++++++++++++++++++++++-------------
drivers/net/designware_eqos.h | 4 +++
2 files changed, 39 insertions(+), 20 deletions(-)
diff --git a/drivers/net/designware_eqos.c b/drivers/net/designware_eqos.c
index 4489725e87..825c8e0140 100644
--- a/drivers/net/designware_eqos.c
+++ b/drivers/net/designware_eqos.c
@@ -172,8 +172,6 @@ struct eqos_dma_regs {
#define EQOS_DESCRIPTOR_SIZE (EQOS_DESCRIPTOR_WORDS * 4)
/* We assume ARCH_DMA_MINALIGN >= 16; 16 is the EQOS HW minimum */
#define EQOS_DESCRIPTOR_ALIGN 64
-#define EQOS_DESCRIPTORS_TX 4
-#define EQOS_DESCRIPTORS_RX 64
#define EQOS_DESCRIPTORS_NUM (EQOS_DESCRIPTORS_TX + EQOS_DESCRIPTORS_RX)
#define EQOS_DESCRIPTORS_SIZE ALIGN(EQOS_DESCRIPTORS_NUM * \
EQOS_DESCRIPTOR_SIZE, EQOS_DESCRIPTOR_ALIGN)
@@ -416,7 +414,7 @@ static int eqos_start(struct eth_device *edev)
{
struct eqos *eqos = edev->priv;
u32 val, tx_fifo_sz, rx_fifo_sz, tqs, rqs, pbl;
- unsigned long last_rx_desc;
+ unsigned long last_rx_rf_desc;
unsigned long rate;
u32 mode_set;
int ret;
@@ -626,9 +624,9 @@ static int eqos_start(struct eth_device *edev)
eqos->tx_currdescnum = eqos->rx_currdescnum = 0;
for (i = 0; i < EQOS_DESCRIPTORS_RX; i++) {
- struct eqos_desc *rx_desc = &eqos->rx_descs[i];
+ struct eqos_desc *rx_rf_desc = &eqos->rx_descs[i];
- writel(EQOS_DESC3_BUF1V | EQOS_DESC3_OWN, &rx_desc->des3);
+ writel(EQOS_DESC3_BUF1V | EQOS_DESC3_OWN, &rx_rf_desc->des3);
}
writel(0, &eqos->dma_regs->ch0_txdesc_list_haddress);
@@ -658,8 +656,8 @@ static int eqos_start(struct eth_device *edev)
* that's not distinguishable from none of the descriptors being
* available.
*/
- last_rx_desc = (ulong)&eqos->rx_descs[(EQOS_DESCRIPTORS_RX - 1)];
- writel(last_rx_desc, &eqos->dma_regs->ch0_rxdesc_tail_pointer);
+ last_rx_rf_desc = (ulong)&eqos->rx_descs[(EQOS_DESCRIPTORS_RX - 1)];
+ writel(last_rx_rf_desc, &eqos->dma_regs->ch0_rxdesc_tail_pointer);
return 0;
}
@@ -746,16 +744,30 @@ static int eqos_send(struct eth_device *edev, void *packet, int length)
static int eqos_recv(struct eth_device *edev)
{
struct eqos *eqos = edev->priv;
- struct eqos_desc *rx_desc;
+ struct eqos_desc *rx_wbf_desc, *rx_rf_desc;
+ dma_addr_t dma;
void *frame;
int length;
- rx_desc = &eqos->rx_descs[eqos->rx_currdescnum];
- if (readl(&rx_desc->des3) & EQOS_DESC3_OWN)
+ /* We have two types of RX descriptors at some pointer: Read and
+ * Write-Back:
+ * All RX descriptors are prepared by the software and given to the
+ * DMA as "Normal" Descriptors with the content as shown in Receive
+ * Normal Descriptor (Read Format). The DMA reads this descriptor and
+ * after transferring a received packet (or part of) to the buffers
+ * indicated by the descriptor, the Rx DMA will close the descriptor
+ * with the corresponding packet status. The format of this status is
+ * given in the "Receive Normal Descriptor (Write-Back Format)"
+ */
+
+ /* Write-Back Format RX descriptor */
+ rx_wbf_desc = &eqos->rx_descs[eqos->rx_currdescnum];
+ if (readl(&rx_wbf_desc->des3) & EQOS_DESC3_OWN)
return 0;
- frame = phys_to_virt(rx_desc->des0);
- length = rx_desc->des3 & 0x7fff;
+ dma = eqos->dma_rx_buf[eqos->rx_currdescnum];
+ frame = phys_to_virt(dma);
+ length = rx_wbf_desc->des3 & 0x7fff;
dma_sync_single_for_cpu(edev->parent, (unsigned long)frame,
length, DMA_FROM_DEVICE);
@@ -763,18 +775,20 @@ static int eqos_recv(struct eth_device *edev)
dma_sync_single_for_device(edev->parent, (unsigned long)frame,
length, DMA_FROM_DEVICE);
- rx_desc->des0 = (unsigned long)frame;
- rx_desc->des1 = 0;
- rx_desc->des2 = 0;
+ /* Read Format RX descriptor */
+ rx_rf_desc = &eqos->rx_descs[eqos->rx_currdescnum];
+ rx_rf_desc->des0 = dma;
+ rx_rf_desc->des1 = 0;
+ rx_rf_desc->des2 = 0;
/*
* Make sure that if HW sees the _OWN write below, it will see all the
* writes to the rest of the descriptor too.
*/
- rx_desc->des3 |= EQOS_DESC3_BUF1V;
- rx_desc->des3 |= EQOS_DESC3_OWN;
+ rx_rf_desc->des3 |= EQOS_DESC3_BUF1V;
+ rx_rf_desc->des3 |= EQOS_DESC3_OWN;
barrier();
- writel((ulong)rx_desc, &eqos->dma_regs->ch0_rxdesc_tail_pointer);
+ writel((ulong)rx_rf_desc, &eqos->dma_regs->ch0_rxdesc_tail_pointer);
eqos->rx_currdescnum++;
eqos->rx_currdescnum %= EQOS_DESCRIPTORS_RX;
@@ -802,7 +816,7 @@ static int eqos_init_resources(struct eqos *eqos)
goto err_free_desc;
for (i = 0; i < EQOS_DESCRIPTORS_RX; i++) {
- struct eqos_desc *rx_desc = &eqos->rx_descs[i];
+ struct eqos_desc *rx_rf_desc = &eqos->rx_descs[i];
dma_addr_t dma;
dma = dma_map_single(edev->parent, p, EQOS_MAX_PACKET_SIZE, DMA_FROM_DEVICE);
@@ -811,7 +825,8 @@ static int eqos_init_resources(struct eqos *eqos)
goto err_free_rx_bufs;
}
- rx_desc->des0 = dma;
+ rx_rf_desc->des0 = dma;
+ eqos->dma_rx_buf[i] = dma;
p += EQOS_MAX_PACKET_SIZE;
}
diff --git a/drivers/net/designware_eqos.h b/drivers/net/designware_eqos.h
index 58ba912cd0..951565e8f9 100644
--- a/drivers/net/designware_eqos.h
+++ b/drivers/net/designware_eqos.h
@@ -40,6 +40,9 @@ struct eqos_dma_regs;
struct eqos_mac_regs;
struct eqos_mtl_regs;
+#define EQOS_DESCRIPTORS_TX 4
+#define EQOS_DESCRIPTORS_RX 64
+
struct eqos {
struct eth_device netdev;
struct mii_bus miibus;
@@ -49,6 +52,7 @@ struct eqos {
u32 tx_currdescnum, rx_currdescnum;
struct eqos_desc *tx_descs, *rx_descs;
+ dma_addr_t dma_rx_buf[EQOS_DESCRIPTORS_RX];
void __iomem *regs;
struct eqos_mac_regs __iomem *mac_regs;
--
2.39.2
More information about the barebox
mailing list