2019-04-26 07:43:04

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 00/12] net: ll_temac: x86_64 support

This patch series adds support for use of ll_temac driver with
platform_data configuration and fixes endianess and 64-bit problems so
that it can be used on x86_64 platform.

A few bugfixes are also included.

Esben Haabendal (12):
net: ll_temac: Fix and simplify error handling by using devres
functions
net: ll_temac: Extend support to non-device-tree platforms
net: ll_temac: Fix support for 64-bit platforms
net: ll_temac: Add support for non-native register endianness
net: ll_temac: Fix support for little-endian platforms
net: ll_temac: Allow use on x86 platforms
net: ll_temac: Support indirect_mutex share within TEMAC IP
net: ll_temac: Fix iommu/swiotlb leak
net: ll_temac: Fix bug causing buffer descriptor overrun
net: ll_temac: Replace bad usage of msleep() with usleep_range()
net: ll_temac: Allow configuration of IRQ coalescing
net: ll_temac: Enable DMA when ready, not before

drivers/net/ethernet/xilinx/Kconfig | 5 +-
drivers/net/ethernet/xilinx/ll_temac.h | 26 +-
drivers/net/ethernet/xilinx/ll_temac_main.c | 514 ++++++++++++++++++----------
drivers/net/ethernet/xilinx/ll_temac_mdio.c | 53 +--
include/linux/xilinx_ll_temac.h | 31 ++
5 files changed, 426 insertions(+), 203 deletions(-)
create mode 100644 include/linux/xilinx_ll_temac.h

--
2.4.11


2019-04-26 07:41:26

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 11/12] net: ll_temac: Allow configuration of IRQ coalescing

This allows custom setup of IRQ coalescing for platforms using legacy
platform_device. The irq timeout and count parameters can be used for
tuning cpu load vs. latency.

I have maintained the 0x00000400 bit in TX_CHNL_CTRL. It is specified as
unused in the documentation I have available. It does not make any
difference in the hardware I have available, so it is left in to not risk
breaking other platforms where it might be used.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac.h | 4 +++
drivers/net/ethernet/xilinx/ll_temac_main.c | 40 ++++++++++++++++++++---------
include/linux/xilinx_ll_temac.h | 5 ++++
3 files changed, 37 insertions(+), 12 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h
index 990f9ed..1aeda08 100644
--- a/drivers/net/ethernet/xilinx/ll_temac.h
+++ b/drivers/net/ethernet/xilinx/ll_temac.h
@@ -375,6 +375,10 @@ struct temac_local {
int tx_bd_next;
int tx_bd_tail;
int rx_bd_ci;
+
+ /* DMA channel control setup */
+ u32 tx_chnl_ctrl;
+ u32 rx_chnl_ctrl;
};

/* Wrappers for temac_ior()/temac_iow() function pointers above */
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index a5ec8ba..a30ac11 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -306,18 +306,15 @@ static int temac_dma_bd_init(struct net_device *ndev)
lp->rx_bd_v[i].app0 = cpu_to_be32(STS_CTRL_APP0_IRQONEND);
}

- lp->dma_out(lp, TX_CHNL_CTRL, 0x10220400 |
- CHNL_CTRL_IRQ_EN |
- CHNL_CTRL_IRQ_DLY_EN |
- CHNL_CTRL_IRQ_COAL_EN);
- /* 0x10220483 */
- /* 0x00100483 */
- lp->dma_out(lp, RX_CHNL_CTRL, 0xff070000 |
- CHNL_CTRL_IRQ_EN |
- CHNL_CTRL_IRQ_DLY_EN |
- CHNL_CTRL_IRQ_COAL_EN |
- CHNL_CTRL_IRQ_IOE);
- /* 0xff010283 */
+ /* Configure DMA channel (irq setup) */
+ lp->dma_out(lp, TX_CHNL_CTRL, lp->tx_chnl_ctrl |
+ 0x00000400 | // Use 1 Bit Wide Counters. Currently Not Used!
+ CHNL_CTRL_IRQ_EN | CHNL_CTRL_IRQ_ERR_EN |
+ CHNL_CTRL_IRQ_DLY_EN | CHNL_CTRL_IRQ_COAL_EN);
+ lp->dma_out(lp, RX_CHNL_CTRL, lp->rx_chnl_ctrl |
+ CHNL_CTRL_IRQ_IOE |
+ CHNL_CTRL_IRQ_EN | CHNL_CTRL_IRQ_ERR_EN |
+ CHNL_CTRL_IRQ_DLY_EN | CHNL_CTRL_IRQ_COAL_EN);

lp->dma_out(lp, RX_CURDESC_PTR, lp->rx_bd_p);
lp->dma_out(lp, RX_TAILDESC_PTR,
@@ -1186,6 +1183,13 @@ static int temac_probe(struct platform_device *pdev)
lp->rx_irq = irq_of_parse_and_map(dma_np, 0);
lp->tx_irq = irq_of_parse_and_map(dma_np, 1);

+ /* Use defaults for IRQ delay/coalescing setup. These
+ * are configuration values, so does not belong in
+ * device-tree.
+ */
+ lp->tx_chnl_ctrl = 0x10220000;
+ lp->rx_chnl_ctrl = 0xff070000;
+
/* Finished with the DMA node; drop the reference */
of_node_put(dma_np);
} else if (pdata) {
@@ -1209,6 +1213,18 @@ static int temac_probe(struct platform_device *pdev)
/* Get DMA RX and TX interrupts */
lp->rx_irq = platform_get_irq(pdev, 0);
lp->tx_irq = platform_get_irq(pdev, 1);
+
+ /* IRQ delay/coalescing setup */
+ if (pdata->tx_irq_timeout || pdata->tx_irq_count)
+ lp->tx_chnl_ctrl = (pdata->tx_irq_timeout << 24) |
+ (pdata->tx_irq_count << 16);
+ else
+ lp->tx_chnl_ctrl = 0x10220000;
+ if (pdata->rx_irq_timeout || pdata->rx_irq_count)
+ lp->rx_chnl_ctrl = (pdata->rx_irq_timeout << 24) |
+ (pdata->rx_irq_count << 16);
+ else
+ lp->rx_chnl_ctrl = 0xff070000;
}

/* Error handle returned DMA RX and TX interrupts */
diff --git a/include/linux/xilinx_ll_temac.h b/include/linux/xilinx_ll_temac.h
index c4c66be..e8d4c09 100644
--- a/include/linux/xilinx_ll_temac.h
+++ b/include/linux/xilinx_ll_temac.h
@@ -21,6 +21,11 @@ struct ll_temac_platform_data {
* they share the same DCR bus bridge.
*/
struct mutex *indirect_mutex;
+ /* DMA channel control setup */
+ u8 tx_irq_timeout; /* TX Interrupt Delay Time-out */
+ u8 tx_irq_count; /* TX Interrupt Coalescing Threshold Count */
+ u8 rx_irq_timeout; /* RX Interrupt Delay Time-out */
+ u8 rx_irq_count; /* RX Interrupt Coalescing Threshold Count */
};

#endif /* __LINUX_XILINX_LL_TEMAC_H */
--
2.4.11

2019-04-26 07:41:35

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 04/12] net: ll_temac: Add support for non-native register endianness

Replace the powerpc specific MMIO register access functions with the
generic big-endian mmio access functions, and add support for
little-endian access depending on configuration.

Big-endian access is maintained as the default, but little-endian can
be configured in device-tree binding or in platform data.

The temac_ior()/temac_iow() functions are replaced with macro wrappers
to avoid modifying existing code more than necessary.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac.h | 12 ++--
drivers/net/ethernet/xilinx/ll_temac_main.c | 87 +++++++++++++++++++++++------
include/linux/xilinx_ll_temac.h | 2 +
3 files changed, 79 insertions(+), 22 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h
index e338b4f..23d8dd5 100644
--- a/drivers/net/ethernet/xilinx/ll_temac.h
+++ b/drivers/net/ethernet/xilinx/ll_temac.h
@@ -347,8 +347,10 @@ struct temac_local {
#ifdef CONFIG_PPC_DCR
dcr_host_t sdma_dcrs;
#endif
- u32 (*dma_in)(struct temac_local *, int);
- void (*dma_out)(struct temac_local *, int, u32);
+ u32 (*temac_ior)(struct temac_local *lp, int offset);
+ void (*temac_iow)(struct temac_local *lp, int offset, u32 value);
+ u32 (*dma_in)(struct temac_local *lp, int reg);
+ void (*dma_out)(struct temac_local *lp, int reg, u32 value);

int tx_irq;
int rx_irq;
@@ -372,9 +374,11 @@ struct temac_local {
int rx_bd_ci;
};

+/* Wrappers for temac_ior()/temac_iow() function pointers above */
+#define temac_ior(lp, o) ((lp)->temac_ior(lp, o))
+#define temac_iow(lp, o, v) ((lp)->temac_iow(lp, o, v))
+
/* xilinx_temac.c */
-u32 temac_ior(struct temac_local *lp, int offset);
-void temac_iow(struct temac_local *lp, int offset, u32 value);
int temac_indirect_busywait(struct temac_local *lp);
u32 temac_indirect_in32(struct temac_local *lp, int reg);
void temac_indirect_out32(struct temac_local *lp, int reg, u32 value);
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index a365bfd..9c1fb9b 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -63,14 +63,24 @@
* Low level register access functions
*/

-u32 temac_ior(struct temac_local *lp, int offset)
+u32 _temac_ior_be(struct temac_local *lp, int offset)
{
- return in_be32(lp->regs + offset);
+ return ioread32be(lp->regs + offset);
}

-void temac_iow(struct temac_local *lp, int offset, u32 value)
+void _temac_iow_be(struct temac_local *lp, int offset, u32 value)
{
- out_be32(lp->regs + offset, value);
+ return iowrite32be(value, lp->regs + offset);
+}
+
+u32 _temac_ior_le(struct temac_local *lp, int offset)
+{
+ return ioread32(lp->regs + offset);
+}
+
+void _temac_iow_le(struct temac_local *lp, int offset, u32 value)
+{
+ return iowrite32(value, lp->regs + offset);
}

int temac_indirect_busywait(struct temac_local *lp)
@@ -121,23 +131,35 @@ void temac_indirect_out32(struct temac_local *lp, int reg, u32 value)
}

/**
- * temac_dma_in32 - Memory mapped DMA read, this function expects a
- * register input that is based on DCR word addresses which
- * are then converted to memory mapped byte addresses
+ * temac_dma_in32_* - Memory mapped DMA read, these function expects a
+ * register input that is based on DCR word addresses which are then
+ * converted to memory mapped byte addresses. To be assigned to
+ * lp->dma_in32.
*/
-static u32 temac_dma_in32(struct temac_local *lp, int reg)
+static u32 temac_dma_in32_be(struct temac_local *lp, int reg)
{
- return in_be32(lp->sdma_regs + (reg << 2));
+ return ioread32be(lp->sdma_regs + (reg << 2));
+}
+
+static u32 temac_dma_in32_le(struct temac_local *lp, int reg)
+{
+ return ioread32(lp->sdma_regs + (reg << 2));
}

/**
- * temac_dma_out32 - Memory mapped DMA read, this function expects a
- * register input that is based on DCR word addresses which
- * are then converted to memory mapped byte addresses
+ * temac_dma_out32_* - Memory mapped DMA read, these function expects
+ * a register input that is based on DCR word addresses which are then
+ * converted to memory mapped byte addresses. To be assigned to
+ * lp->dma_out32.
*/
-static void temac_dma_out32(struct temac_local *lp, int reg, u32 value)
+static void temac_dma_out32_be(struct temac_local *lp, int reg, u32 value)
+{
+ iowrite32be(value, lp->sdma_regs + (reg << 2));
+}
+
+static void temac_dma_out32_le(struct temac_local *lp, int reg, u32 value)
{
- out_be32(lp->sdma_regs + (reg << 2), value);
+ iowrite32(value, lp->sdma_regs + (reg << 2));
}

/* DMA register access functions can be DCR based or memory mapped.
@@ -1024,6 +1046,7 @@ static int temac_probe(struct platform_device *pdev)
struct resource *res;
const void *addr;
__be32 *p;
+ bool little_endian;
int rc = 0;

/* Init network device structure */
@@ -1068,6 +1091,24 @@ static int temac_probe(struct platform_device *pdev)
return PTR_ERR(lp->regs);
}

+ /* Select register access functions with the specified
+ * endianness mode. Default for OF devices is big-endian.
+ */
+ little_endian = false;
+ if (temac_np) {
+ if (of_get_property(temac_np, "little-endian", NULL))
+ little_endian = true;
+ } else if (pdata) {
+ little_endian = pdata->reg_little_endian;
+ }
+ if (little_endian) {
+ lp->temac_ior = _temac_ior_le;
+ lp->temac_iow = _temac_iow_le;
+ } else {
+ lp->temac_ior = _temac_ior_be;
+ lp->temac_iow = _temac_iow_be;
+ }
+
/* Setup checksum offload, but default to off if not specified */
lp->temac_features = 0;
if (temac_np) {
@@ -1111,8 +1152,13 @@ static int temac_probe(struct platform_device *pdev)
of_node_put(dma_np);
return PTR_ERR(lp->sdma_regs);
}
- lp->dma_in = temac_dma_in32;
- lp->dma_out = temac_dma_out32;
+ if (of_get_property(dma_np, "little-endian", NULL)) {
+ lp->dma_in = temac_dma_in32_le;
+ lp->dma_out = temac_dma_out32_le;
+ } else {
+ lp->dma_in = temac_dma_in32_be;
+ lp->dma_out = temac_dma_out32_be;
+ }
dev_dbg(&pdev->dev, "MEM base: %p\n", lp->sdma_regs);
}

@@ -1132,8 +1178,13 @@ static int temac_probe(struct platform_device *pdev)
"could not map DMA registers\n");
return PTR_ERR(lp->sdma_regs);
}
- lp->dma_in = temac_dma_in32;
- lp->dma_out = temac_dma_out32;
+ if (pdata->dma_little_endian) {
+ lp->dma_in = temac_dma_in32_le;
+ lp->dma_out = temac_dma_out32_le;
+ } else {
+ lp->dma_in = temac_dma_in32_be;
+ lp->dma_out = temac_dma_out32_be;
+ }

/* Get DMA RX and TX interrupts */
lp->rx_irq = platform_get_irq(pdev, 0);
diff --git a/include/linux/xilinx_ll_temac.h b/include/linux/xilinx_ll_temac.h
index 44d7dd6..53f9203 100644
--- a/include/linux/xilinx_ll_temac.h
+++ b/include/linux/xilinx_ll_temac.h
@@ -13,6 +13,8 @@ struct ll_temac_platform_data {
unsigned long long mdio_bus_id; /* Unique id for MDIO bus */
int phy_addr; /* Address of the PHY to connect to */
phy_interface_t phy_interface; /* PHY interface mode */
+ bool reg_little_endian; /* Little endian TEMAC register access */
+ bool dma_little_endian; /* Little endian DMA register access */
};

#endif /* __LINUX_XILINX_LL_TEMAC_H */
--
2.4.11

2019-04-26 07:41:52

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 05/12] net: ll_temac: Fix support for little-endian platforms

Both TEMAC and SDMA is big-endian, so make sure that all values in SDMA
buffer descriptors (cmdac_bd) are handled as big-endian, independent of the
host endianness. With all currently supported platforms being big-endian,
this change does not make a change for any of them.

Note, when using app3 and app4 for piggybacking skb pointers there is no
need to care about endianness, as neither TEMAC nor SDMA access app3 and
app4 in TX buffer descriptors.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac_main.c | 90 ++++++++++++++++-------------
1 file changed, 51 insertions(+), 39 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 9c1fb9b..817bc57 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -258,6 +258,7 @@ static int temac_dma_bd_init(struct net_device *ndev)
{
struct temac_local *lp = netdev_priv(ndev);
struct sk_buff *skb;
+ dma_addr_t skb_dma_addr;
int i;

lp->rx_skb = devm_kcalloc(&ndev->dev, RX_BD_NUM, sizeof(*lp->rx_skb),
@@ -280,13 +281,15 @@ static int temac_dma_bd_init(struct net_device *ndev)
goto out;

for (i = 0; i < TX_BD_NUM; i++) {
- lp->tx_bd_v[i].next = lp->tx_bd_p +
- sizeof(*lp->tx_bd_v) * ((i + 1) % TX_BD_NUM);
+ lp->tx_bd_v[i].next = cpu_to_be32(
+ lp->tx_bd_p
+ + sizeof(*lp->tx_bd_v) * ((i + 1) % TX_BD_NUM));
}

for (i = 0; i < RX_BD_NUM; i++) {
- lp->rx_bd_v[i].next = lp->rx_bd_p +
- sizeof(*lp->rx_bd_v) * ((i + 1) % RX_BD_NUM);
+ lp->rx_bd_v[i].next = cpu_to_be32(
+ lp->rx_bd_p
+ + sizeof(*lp->rx_bd_v) * ((i + 1) % RX_BD_NUM));

skb = netdev_alloc_skb_ip_align(ndev,
XTE_MAX_JUMBO_FRAME_SIZE);
@@ -295,12 +298,12 @@ static int temac_dma_bd_init(struct net_device *ndev)

lp->rx_skb[i] = skb;
/* returns physical address of skb->data */
- lp->rx_bd_v[i].phys = dma_map_single(ndev->dev.parent,
- skb->data,
- XTE_MAX_JUMBO_FRAME_SIZE,
- DMA_FROM_DEVICE);
- lp->rx_bd_v[i].len = XTE_MAX_JUMBO_FRAME_SIZE;
- lp->rx_bd_v[i].app0 = STS_CTRL_APP0_IRQONEND;
+ skb_dma_addr = dma_map_single(ndev->dev.parent, skb->data,
+ XTE_MAX_JUMBO_FRAME_SIZE,
+ DMA_FROM_DEVICE);
+ lp->rx_bd_v[i].phys = cpu_to_be32(skb_dma_addr);
+ lp->rx_bd_v[i].len = cpu_to_be32(XTE_MAX_JUMBO_FRAME_SIZE);
+ lp->rx_bd_v[i].app0 = cpu_to_be32(STS_CTRL_APP0_IRQONEND);
}

lp->dma_out(lp, TX_CHNL_CTRL, 0x10220400 |
@@ -676,11 +679,11 @@ static void temac_start_xmit_done(struct net_device *ndev)
struct sk_buff *skb;

cur_p = &lp->tx_bd_v[lp->tx_bd_ci];
- stat = cur_p->app0;
+ stat = be32_to_cpu(cur_p->app0);

while (stat & STS_CTRL_APP0_CMPLT) {
- dma_unmap_single(ndev->dev.parent, cur_p->phys, cur_p->len,
- DMA_TO_DEVICE);
+ dma_unmap_single(ndev->dev.parent, be32_to_cpu(cur_p->phys),
+ be32_to_cpu(cur_p->len), DMA_TO_DEVICE);
skb = (struct sk_buff *)ptr_from_txbd(cur_p);
if (skb)
dev_consume_skb_irq(skb);
@@ -691,14 +694,14 @@ static void temac_start_xmit_done(struct net_device *ndev)
cur_p->app4 = 0;

ndev->stats.tx_packets++;
- ndev->stats.tx_bytes += cur_p->len;
+ ndev->stats.tx_bytes += be32_to_cpu(cur_p->len);

lp->tx_bd_ci++;
if (lp->tx_bd_ci >= TX_BD_NUM)
lp->tx_bd_ci = 0;

cur_p = &lp->tx_bd_v[lp->tx_bd_ci];
- stat = cur_p->app0;
+ stat = be32_to_cpu(cur_p->app0);
}

netif_wake_queue(ndev);
@@ -732,7 +735,7 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
{
struct temac_local *lp = netdev_priv(ndev);
struct cdmac_bd *cur_p;
- dma_addr_t start_p, tail_p;
+ dma_addr_t start_p, tail_p, skb_dma_addr;
int ii;
unsigned long num_frag;
skb_frag_t *frag;
@@ -753,15 +756,17 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
unsigned int csum_start_off = skb_checksum_start_offset(skb);
unsigned int csum_index_off = csum_start_off + skb->csum_offset;

- cur_p->app0 |= 1; /* TX Checksum Enabled */
- cur_p->app1 = (csum_start_off << 16) | csum_index_off;
+ cur_p->app0 |= cpu_to_be32(0x000001); /* TX Checksum Enabled */
+ cur_p->app1 = cpu_to_be32((csum_start_off << 16)
+ | csum_index_off);
cur_p->app2 = 0; /* initial checksum seed */
}

- cur_p->app0 |= STS_CTRL_APP0_SOP;
- cur_p->len = skb_headlen(skb);
- cur_p->phys = dma_map_single(ndev->dev.parent, skb->data,
- skb_headlen(skb), DMA_TO_DEVICE);
+ cur_p->app0 |= cpu_to_be32(STS_CTRL_APP0_SOP);
+ skb_dma_addr = dma_map_single(ndev->dev.parent, skb->data,
+ skb_headlen(skb), DMA_TO_DEVICE);
+ cur_p->len = cpu_to_be32(skb_headlen(skb));
+ cur_p->phys = cpu_to_be32(skb_dma_addr);
ptr_to_txbd((void *)skb, cur_p);

for (ii = 0; ii < num_frag; ii++) {
@@ -770,14 +775,15 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
lp->tx_bd_tail = 0;

cur_p = &lp->tx_bd_v[lp->tx_bd_tail];
- cur_p->phys = dma_map_single(ndev->dev.parent,
- skb_frag_address(frag),
- skb_frag_size(frag), DMA_TO_DEVICE);
- cur_p->len = skb_frag_size(frag);
+ skb_dma_addr = dma_map_single(
+ ndev->dev.parent, skb_frag_address(frag),
+ skb_frag_size(frag), DMA_TO_DEVICE);
+ cur_p->phys = cpu_to_be32(skb_dma_addr);
+ cur_p->len = cpu_to_be32(skb_frag_size(frag));
cur_p->app0 = 0;
frag++;
}
- cur_p->app0 |= STS_CTRL_APP0_EOP;
+ cur_p->app0 |= cpu_to_be32(STS_CTRL_APP0_EOP);

tail_p = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * lp->tx_bd_tail;
lp->tx_bd_tail++;
@@ -799,7 +805,7 @@ static void ll_temac_recv(struct net_device *ndev)
struct sk_buff *skb, *new_skb;
unsigned int bdstat;
struct cdmac_bd *cur_p;
- dma_addr_t tail_p;
+ dma_addr_t tail_p, skb_dma_addr;
int length;
unsigned long flags;

@@ -808,14 +814,14 @@ static void ll_temac_recv(struct net_device *ndev)
tail_p = lp->rx_bd_p + sizeof(*lp->rx_bd_v) * lp->rx_bd_ci;
cur_p = &lp->rx_bd_v[lp->rx_bd_ci];

- bdstat = cur_p->app0;
+ bdstat = be32_to_cpu(cur_p->app0);
while ((bdstat & STS_CTRL_APP0_CMPLT)) {

skb = lp->rx_skb[lp->rx_bd_ci];
- length = cur_p->app4 & 0x3FFF;
+ length = be32_to_cpu(cur_p->app4) & 0x3FFF;

- dma_unmap_single(ndev->dev.parent, cur_p->phys, length,
- DMA_FROM_DEVICE);
+ dma_unmap_single(ndev->dev.parent, be32_to_cpu(cur_p->phys),
+ length, DMA_FROM_DEVICE);

skb_put(skb, length);
skb->protocol = eth_type_trans(skb, ndev);
@@ -826,7 +832,12 @@ static void ll_temac_recv(struct net_device *ndev)
(skb->protocol == htons(ETH_P_IP)) &&
(skb->len > 64)) {

- skb->csum = cur_p->app3 & 0xFFFF;
+ /* Convert from device endianness (be32) to cpu
+ * endiannes, and if necessary swap the bytes
+ * (back) for proper IP checksum byte order
+ * (be16).
+ */
+ skb->csum = htons(be32_to_cpu(cur_p->app3) & 0xFFFF);
skb->ip_summed = CHECKSUM_COMPLETE;
}

@@ -843,11 +854,12 @@ static void ll_temac_recv(struct net_device *ndev)
return;
}

- cur_p->app0 = STS_CTRL_APP0_IRQONEND;
- cur_p->phys = dma_map_single(ndev->dev.parent, new_skb->data,
- XTE_MAX_JUMBO_FRAME_SIZE,
- DMA_FROM_DEVICE);
- cur_p->len = XTE_MAX_JUMBO_FRAME_SIZE;
+ cur_p->app0 = cpu_to_be32(STS_CTRL_APP0_IRQONEND);
+ skb_dma_addr = dma_map_single(ndev->dev.parent, new_skb->data,
+ XTE_MAX_JUMBO_FRAME_SIZE,
+ DMA_FROM_DEVICE);
+ cur_p->phys = cpu_to_be32(skb_dma_addr);
+ cur_p->len = cpu_to_be32(XTE_MAX_JUMBO_FRAME_SIZE);
lp->rx_skb[lp->rx_bd_ci] = new_skb;

lp->rx_bd_ci++;
@@ -855,7 +867,7 @@ static void ll_temac_recv(struct net_device *ndev)
lp->rx_bd_ci = 0;

cur_p = &lp->rx_bd_v[lp->rx_bd_ci];
- bdstat = cur_p->app0;
+ bdstat = be32_to_cpu(cur_p->app0);
}
lp->dma_out(lp, RX_TAILDESC_PTR, tail_p);

--
2.4.11

2019-04-26 07:41:54

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 07/12] net: ll_temac: Support indirect_mutex share within TEMAC IP

Indirect register access goes through a DCR bus bridge, which
allows only one outstanding transaction. And to make matters
worse, each TEMAC IP block contains two Ethernet interfaces, and
although they seem to have separate registers for indirect access,
they actually share the registers. Or to be more specific, MSW, LSW
and CTL registers are physically shared between Ethernet interfaces
in same TEMAC IP, with RDY register being (almost) specificic to
the Ethernet interface. The 0x10000 bit in RDY reflects combined
bus ready state though.

So we need to take care to synchronize not only within a single
device, but also between devices in same TEMAC IP.

This commit allows to do that with legacy platform devices.

For OF devices, the xlnx,compound parent of the temac node should be
used to find siblings, and setup a shared indirect_mutex between them.
I will leave this work to somebody else, as I don't have hardware to
test that. No regression is introduced by that, as before this commit
using two Ethernet interfaces in same TEMAC block is simply broken.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac.h | 5 ++++-
drivers/net/ethernet/xilinx/ll_temac_main.c | 30 ++++++++++++++++++-----------
drivers/net/ethernet/xilinx/ll_temac_mdio.c | 16 +++++++--------
include/linux/xilinx_ll_temac.h | 6 ++++++
4 files changed, 37 insertions(+), 20 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h
index 23d8dd5..990f9ed 100644
--- a/drivers/net/ethernet/xilinx/ll_temac.h
+++ b/drivers/net/ethernet/xilinx/ll_temac.h
@@ -358,7 +358,10 @@ struct temac_local {

struct sk_buff **rx_skb;
spinlock_t rx_lock;
- struct mutex indirect_mutex;
+ /* For synchronization of indirect register access. Must be
+ * shared mutex between interfaces in same TEMAC block.
+ */
+ struct mutex *indirect_mutex;
u32 options; /* Current options word */
int last_link;
unsigned int temac_features;
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 817bc57..309f149 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -346,7 +346,7 @@ static void temac_do_set_mac_address(struct net_device *ndev)
struct temac_local *lp = netdev_priv(ndev);

/* set up unicast MAC address filter set its mac address */
- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
temac_indirect_out32(lp, XTE_UAW0_OFFSET,
(ndev->dev_addr[0]) |
(ndev->dev_addr[1] << 8) |
@@ -357,7 +357,7 @@ static void temac_do_set_mac_address(struct net_device *ndev)
temac_indirect_out32(lp, XTE_UAW1_OFFSET,
(ndev->dev_addr[4] & 0x000000ff) |
(ndev->dev_addr[5] << 8));
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);
}

static int temac_init_mac_address(struct net_device *ndev, const void *address)
@@ -386,7 +386,7 @@ static void temac_set_multicast_list(struct net_device *ndev)
u32 multi_addr_msw, multi_addr_lsw, val;
int i;

- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
if (ndev->flags & (IFF_ALLMULTI | IFF_PROMISC) ||
netdev_mc_count(ndev) > MULTICAST_CAM_TABLE_NUM) {
/*
@@ -425,7 +425,7 @@ static void temac_set_multicast_list(struct net_device *ndev)
temac_indirect_out32(lp, XTE_MAW1_OFFSET, 0);
dev_info(&ndev->dev, "Promiscuous mode disabled.\n");
}
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);
}

static struct temac_option {
@@ -517,7 +517,7 @@ static u32 temac_setoptions(struct net_device *ndev, u32 options)
struct temac_option *tp = &temac_options[0];
int reg;

- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
while (tp->opt) {
reg = temac_indirect_in32(lp, tp->reg) & ~tp->m_or;
if (options & tp->opt)
@@ -526,7 +526,7 @@ static u32 temac_setoptions(struct net_device *ndev, u32 options)
tp++;
}
lp->options |= options;
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);

return 0;
}
@@ -545,7 +545,7 @@ static void temac_device_reset(struct net_device *ndev)

dev_dbg(&ndev->dev, "%s()\n", __func__);

- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
/* Reset the receiver and wait for it to finish reset */
temac_indirect_out32(lp, XTE_RXC1_OFFSET, XTE_RXC1_RXRST_MASK);
timeout = 1000;
@@ -597,7 +597,7 @@ static void temac_device_reset(struct net_device *ndev)
temac_indirect_out32(lp, XTE_TXC_OFFSET, 0);
temac_indirect_out32(lp, XTE_FCC_OFFSET, XTE_FCC_RXFLO_MASK);

- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);

/* Sync default options with HW
* but leave receiver and transmitter disabled. */
@@ -625,7 +625,7 @@ static void temac_adjust_link(struct net_device *ndev)
/* hash together the state values to decide if something has changed */
link_state = phy->speed | (phy->duplex << 1) | phy->link;

- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
if (lp->last_link != link_state) {
mii_speed = temac_indirect_in32(lp, XTE_EMCFG_OFFSET);
mii_speed &= ~XTE_EMCFG_LINKSPD_MASK;
@@ -641,7 +641,7 @@ static void temac_adjust_link(struct net_device *ndev)
lp->last_link = link_state;
phy_print_status(phy);
}
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);
}

#ifdef CONFIG_64BIT
@@ -1092,7 +1092,15 @@ static int temac_probe(struct platform_device *pdev)
lp->dev = &pdev->dev;
lp->options = XTE_OPTION_DEFAULTS;
spin_lock_init(&lp->rx_lock);
- mutex_init(&lp->indirect_mutex);
+
+ /* Setup mutex for synchronization of indirect register access */
+ if (pdata && pdata->indirect_mutex) {
+ lp->indirect_mutex = pdata->indirect_mutex;
+ } else {
+ lp->indirect_mutex = devm_kmalloc(
+ &pdev->dev, sizeof(*lp->indirect_mutex), GFP_KERNEL);
+ mutex_init(lp->indirect_mutex);
+ }

/* map device registers */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
diff --git a/drivers/net/ethernet/xilinx/ll_temac_mdio.c b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
index 61b832f..c8707f1 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_mdio.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
@@ -29,10 +29,10 @@ static int temac_mdio_read(struct mii_bus *bus, int phy_id, int reg)
/* Write the PHY address to the MIIM Access Initiator register.
* When the transfer completes, the PHY register value will appear
* in the LSW0 register */
- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
temac_iow(lp, XTE_LSW0_OFFSET, (phy_id << 5) | reg);
rc = temac_indirect_in32(lp, XTE_MIIMAI_OFFSET);
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);

dev_dbg(lp->dev, "temac_mdio_read(phy_id=%i, reg=%x) == %x\n",
phy_id, reg, rc);
@@ -50,10 +50,10 @@ static int temac_mdio_write(struct mii_bus *bus, int phy_id, int reg, u16 val)
/* First write the desired value into the write data register
* and then write the address into the access initiator register
*/
- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
temac_indirect_out32(lp, XTE_MGTDR_OFFSET, val);
temac_indirect_out32(lp, XTE_MIIMAI_OFFSET, (phy_id << 5) | reg);
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);

return 0;
}
@@ -87,9 +87,9 @@ int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)

/* Enable the MDIO bus by asserting the enable bit and writing
* in the clock config */
- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
temac_indirect_out32(lp, XTE_MC_OFFSET, 1 << 6 | clk_div);
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);

bus = devm_mdiobus_alloc(&pdev->dev);
if (!bus)
@@ -116,10 +116,10 @@ int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)
if (rc)
return rc;

- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
dev_dbg(lp->dev, "MDIO bus registered; MC:%x\n",
temac_indirect_in32(lp, XTE_MC_OFFSET));
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);
return 0;
}

diff --git a/include/linux/xilinx_ll_temac.h b/include/linux/xilinx_ll_temac.h
index 53f9203..c4c66be 100644
--- a/include/linux/xilinx_ll_temac.h
+++ b/include/linux/xilinx_ll_temac.h
@@ -15,6 +15,12 @@ struct ll_temac_platform_data {
phy_interface_t phy_interface; /* PHY interface mode */
bool reg_little_endian; /* Little endian TEMAC register access */
bool dma_little_endian; /* Little endian DMA register access */
+ /* Pre-initialized mutex to use for synchronizing indirect
+ * register access. When using both interfaces of a single
+ * TEMAC IP block, the same mutex should be passed here, as
+ * they share the same DCR bus bridge.
+ */
+ struct mutex *indirect_mutex;
};

#endif /* __LINUX_XILINX_LL_TEMAC_H */
--
2.4.11

2019-04-26 07:42:08

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 02/12] net: ll_temac: Extend support to non-device-tree platforms

Support initialization with platdata, so the driver can be used on
non-device-tree platforms.

For currently supported device-tree platforms, the driver should behave
as before.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac.h | 3 +
drivers/net/ethernet/xilinx/ll_temac_main.c | 187 +++++++++++++++++++---------
drivers/net/ethernet/xilinx/ll_temac_mdio.c | 23 +++-
include/linux/xilinx_ll_temac.h | 18 +++
4 files changed, 165 insertions(+), 66 deletions(-)
create mode 100644 include/linux/xilinx_ll_temac.h

diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h
index 4557578..e338b4f 100644
--- a/drivers/net/ethernet/xilinx/ll_temac.h
+++ b/drivers/net/ethernet/xilinx/ll_temac.h
@@ -334,6 +334,9 @@ struct temac_local {

/* Connection to PHY device */
struct device_node *phy_node;
+ /* For non-device-tree devices */
+ char phy_name[MII_BUS_ID_SIZE + 3];
+ phy_interface_t phy_interface;

/* MDIO bus data */
struct mii_bus *mii_bus; /* MII bus reference */
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index c4e85a9..4594fe3 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -33,6 +33,7 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/netdevice.h>
+#include <linux/if_ether.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
@@ -51,6 +52,7 @@
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
+#include <linux/xilinx_ll_temac.h>

#include "ll_temac.h"

@@ -187,7 +189,7 @@ static int temac_dcr_setup(struct temac_local *lp, struct platform_device *op,

/*
* temac_dcr_setup - This is a stub for when DCR is not supported,
- * such as with MicroBlaze
+ * such as with MicroBlaze and x86
*/
static int temac_dcr_setup(struct temac_local *lp, struct platform_device *op,
struct device_node *np)
@@ -857,7 +859,14 @@ static int temac_open(struct net_device *ndev)
dev_err(lp->dev, "of_phy_connect() failed\n");
return -ENODEV;
}
-
+ phy_start(phydev);
+ } else if (strlen(lp->phy_name) > 0) {
+ phydev = phy_connect(lp->ndev, lp->phy_name, temac_adjust_link,
+ lp->phy_interface);
+ if (!phydev) {
+ dev_err(lp->dev, "phy_connect() failed\n");
+ return -ENODEV;
+ }
phy_start(phydev);
}

@@ -977,11 +986,13 @@ static const struct ethtool_ops temac_ethtool_ops = {
.set_link_ksettings = phy_ethtool_set_link_ksettings,
};

-static int temac_of_probe(struct platform_device *op)
+static int temac_probe(struct platform_device *pdev)
{
- struct device_node *np;
+ struct ll_temac_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct device_node *temac_np = dev_of_node(&pdev->dev), *dma_np;
struct temac_local *lp;
struct net_device *ndev;
+ struct resource *res;
const void *addr;
__be32 *p;
int rc = 0;
@@ -991,8 +1002,8 @@ static int temac_of_probe(struct platform_device *op)
if (!ndev)
return -ENOMEM;

- platform_set_drvdata(op, ndev);
- SET_NETDEV_DEV(ndev, &op->dev);
+ platform_set_drvdata(pdev, ndev);
+ SET_NETDEV_DEV(ndev, &pdev->dev);
ndev->flags &= ~IFF_MULTICAST; /* clear multicast */
ndev->features = NETIF_F_SG;
ndev->netdev_ops = &temac_netdev_ops;
@@ -1014,79 +1025,129 @@ static int temac_of_probe(struct platform_device *op)
/* setup temac private info structure */
lp = netdev_priv(ndev);
lp->ndev = ndev;
- lp->dev = &op->dev;
+ lp->dev = &pdev->dev;
lp->options = XTE_OPTION_DEFAULTS;
spin_lock_init(&lp->rx_lock);
mutex_init(&lp->indirect_mutex);

/* map device registers */
- lp->regs = devm_of_iomap(&op->dev, op->dev.of_node, 0, NULL);
- if (!lp->regs) {
- dev_err(&op->dev, "could not map temac regs.\n");
- return -ENOMEM;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ lp->regs = devm_ioremap_nocache(&pdev->dev, res->start,
+ resource_size(res));
+ if (IS_ERR(lp->regs)) {
+ dev_err(&pdev->dev, "could not map TEMAC registers\n");
+ return PTR_ERR(lp->regs);
}

/* Setup checksum offload, but default to off if not specified */
lp->temac_features = 0;
- p = (__be32 *)of_get_property(op->dev.of_node, "xlnx,txcsum", NULL);
- if (p && be32_to_cpu(*p)) {
- lp->temac_features |= TEMAC_FEATURE_TX_CSUM;
+ if (temac_np) {
+ p = (__be32 *)of_get_property(temac_np, "xlnx,txcsum", NULL);
+ if (p && be32_to_cpu(*p))
+ lp->temac_features |= TEMAC_FEATURE_TX_CSUM;
+ p = (__be32 *)of_get_property(temac_np, "xlnx,rxcsum", NULL);
+ if (p && be32_to_cpu(*p))
+ lp->temac_features |= TEMAC_FEATURE_RX_CSUM;
+ } else if (pdata) {
+ if (pdata->txcsum)
+ lp->temac_features |= TEMAC_FEATURE_TX_CSUM;
+ if (pdata->rxcsum)
+ lp->temac_features |= TEMAC_FEATURE_RX_CSUM;
+ }
+ if (lp->temac_features & TEMAC_FEATURE_TX_CSUM)
/* Can checksum TCP/UDP over IPv4. */
ndev->features |= NETIF_F_IP_CSUM;
- }
- p = (__be32 *)of_get_property(op->dev.of_node, "xlnx,rxcsum", NULL);
- if (p && be32_to_cpu(*p))
- lp->temac_features |= TEMAC_FEATURE_RX_CSUM;
-
- /* Find the DMA node, map the DMA registers, and decode the DMA IRQs */
- np = of_parse_phandle(op->dev.of_node, "llink-connected", 0);
- if (!np) {
- dev_err(&op->dev, "could not find DMA node\n");
- return -ENODEV;
- }

- /* Setup the DMA register accesses, could be DCR or memory mapped */
- if (temac_dcr_setup(lp, op, np)) {
+ /* Setup LocalLink DMA */
+ if (temac_np) {
+ /* Find the DMA node, map the DMA registers, and
+ * decode the DMA IRQs.
+ */
+ dma_np = of_parse_phandle(temac_np, "llink-connected", 0);
+ if (!dma_np) {
+ dev_err(&pdev->dev, "could not find DMA node\n");
+ return -ENODEV;
+ }

- /* no DCR in the device tree, try non-DCR */
- lp->sdma_regs = devm_of_iomap(&op->dev, np, 0, NULL);
- if (lp->sdma_regs) {
+ /* Setup the DMA register accesses, could be DCR or
+ * memory mapped.
+ */
+ if (temac_dcr_setup(lp, pdev, dma_np)) {
+ /* no DCR in the device tree, try non-DCR */
+ lp->sdma_regs = devm_of_iomap(&pdev->dev, dma_np, 0,
+ NULL);
+ if (IS_ERR(lp->sdma_regs)) {
+ dev_err(&pdev->dev,
+ "unable to map DMA registers\n");
+ of_node_put(dma_np);
+ return PTR_ERR(lp->sdma_regs);
+ }
lp->dma_in = temac_dma_in32;
lp->dma_out = temac_dma_out32;
- dev_dbg(&op->dev, "MEM base: %p\n", lp->sdma_regs);
- } else {
- dev_err(&op->dev, "unable to map DMA registers\n");
- of_node_put(np);
- return -ENOMEM;
+ dev_dbg(&pdev->dev, "MEM base: %p\n", lp->sdma_regs);
}
- }
-
- lp->rx_irq = irq_of_parse_and_map(np, 0);
- lp->tx_irq = irq_of_parse_and_map(np, 1);

- of_node_put(np); /* Finished with the DMA node; drop the reference */
+ /* Get DMA RX and TX interrupts */
+ lp->rx_irq = irq_of_parse_and_map(dma_np, 0);
+ lp->tx_irq = irq_of_parse_and_map(dma_np, 1);
+
+ /* Finished with the DMA node; drop the reference */
+ of_node_put(dma_np);
+ } else if (pdata) {
+ /* 2nd memory resource specifies DMA registers */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ lp->sdma_regs = devm_ioremap_nocache(&pdev->dev, res->start,
+ resource_size(res));
+ if (IS_ERR(lp->sdma_regs)) {
+ dev_err(&pdev->dev,
+ "could not map DMA registers\n");
+ return PTR_ERR(lp->sdma_regs);
+ }
+ lp->dma_in = temac_dma_in32;
+ lp->dma_out = temac_dma_out32;

- if (!lp->rx_irq || !lp->tx_irq) {
- dev_err(&op->dev, "could not determine irqs\n");
- return -ENOMEM;
+ /* Get DMA RX and TX interrupts */
+ lp->rx_irq = platform_get_irq(pdev, 0);
+ lp->tx_irq = platform_get_irq(pdev, 1);
}

+ /* Error handle returned DMA RX and TX interrupts */
+ if (lp->rx_irq < 0) {
+ if (lp->rx_irq != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "could not get DMA RX irq\n");
+ return lp->rx_irq;
+ }
+ if (lp->tx_irq < 0) {
+ if (lp->tx_irq != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "could not get DMA TX irq\n");
+ return lp->tx_irq;
+ }

- /* Retrieve the MAC address */
- addr = of_get_mac_address(op->dev.of_node);
- if (!addr) {
- dev_err(&op->dev, "could not find MAC address\n");
- return -ENODEV;
+ if (temac_np) {
+ /* Retrieve the MAC address */
+ addr = of_get_mac_address(temac_np);
+ if (!addr) {
+ dev_err(&pdev->dev, "could not find MAC address\n");
+ return -ENODEV;
+ }
+ temac_init_mac_address(ndev, addr);
+ } else if (pdata) {
+ temac_init_mac_address(ndev, pdata->mac_addr);
}
- temac_init_mac_address(ndev, addr);

rc = temac_mdio_setup(lp, pdev);
if (rc)
- dev_warn(&op->dev, "error registering MDIO bus\n");
-
- lp->phy_node = of_parse_phandle(op->dev.of_node, "phy-handle", 0);
- if (lp->phy_node)
- dev_dbg(lp->dev, "using PHY node %pOF (%p)\n", np, np);
+ dev_warn(&pdev->dev, "error registering MDIO bus\n");
+
+ if (temac_np) {
+ lp->phy_node = of_parse_phandle(temac_np, "phy-handle", 0);
+ if (lp->phy_node)
+ dev_dbg(lp->dev, "using PHY node %pOF\n", temac_np);
+ } else if (pdata) {
+ snprintf(lp->phy_name, sizeof(lp->phy_name),
+ PHY_ID_FMT, lp->mii_bus->id, pdata->phy_addr);
+ lp->phy_interface = pdata->phy_interface;
+ }

/* Add the device attributes */
rc = sysfs_create_group(&lp->dev->kobj, &temac_attr_group);
@@ -1106,19 +1167,21 @@ static int temac_of_probe(struct platform_device *op)
err_register_ndev:
sysfs_remove_group(&lp->dev->kobj, &temac_attr_group);
err_sysfs_create:
- of_node_put(lp->phy_node);
+ if (lp->phy_node)
+ of_node_put(lp->phy_node);
temac_mdio_teardown(lp);
return rc;
}

-static int temac_of_remove(struct platform_device *op)
+static int temac_remove(struct platform_device *pdev)
{
- struct net_device *ndev = platform_get_drvdata(op);
+ struct net_device *ndev = platform_get_drvdata(pdev);
struct temac_local *lp = netdev_priv(ndev);

unregister_netdev(ndev);
sysfs_remove_group(&lp->dev->kobj, &temac_attr_group);
- of_node_put(lp->phy_node);
+ if (lp->phy_node)
+ of_node_put(lp->phy_node);
temac_mdio_teardown(lp);
return 0;
}
@@ -1132,16 +1195,16 @@ static const struct of_device_id temac_of_match[] = {
};
MODULE_DEVICE_TABLE(of, temac_of_match);

-static struct platform_driver temac_of_driver = {
- .probe = temac_of_probe,
- .remove = temac_of_remove,
+static struct platform_driver temac_driver = {
+ .probe = temac_probe,
+ .remove = temac_remove,
.driver = {
.name = "xilinx_temac",
.of_match_table = temac_of_match,
},
};

-module_platform_driver(temac_of_driver);
+module_platform_driver(temac_driver);

MODULE_DESCRIPTION("Xilinx LL_TEMAC Ethernet driver");
MODULE_AUTHOR("Yoshio Kashiwagi");
diff --git a/drivers/net/ethernet/xilinx/ll_temac_mdio.c b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
index a0b365e..61b832f 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_mdio.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
@@ -14,6 +14,7 @@
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/of_mdio.h>
+#include <linux/xilinx_ll_temac.h>

#include "ll_temac.h"

@@ -59,6 +60,7 @@ static int temac_mdio_write(struct mii_bus *bus, int phy_id, int reg, u16 val)

int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)
{
+ struct ll_temac_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct device_node *np = dev_of_node(&pdev->dev);
struct mii_bus *bus;
u32 bus_hz;
@@ -66,9 +68,16 @@ int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)
int rc;
struct resource res;

+ /* Get MDIO bus frequency (if specified) */
+ bus_hz = 0;
+ if (np)
+ of_property_read_u32(np, "clock-frequency", &bus_hz);
+ else if (pdata)
+ bus_hz = pdata->mdio_clk_freq;
+
/* Calculate a reasonable divisor for the clock rate */
clk_div = 0x3f; /* worst-case default setting */
- if (of_property_read_u32(np, "clock-frequency", &bus_hz) == 0) {
+ if (bus_hz != 0) {
clk_div = bus_hz / (2500 * 1000 * 2) - 1;
if (clk_div < 1)
clk_div = 1;
@@ -86,9 +95,15 @@ int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)
if (!bus)
return -ENOMEM;

- of_address_to_resource(np, 0, &res);
- snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx",
- (unsigned long long)res.start);
+ if (np) {
+ of_address_to_resource(np, 0, &res);
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx",
+ (unsigned long long)res.start);
+ } else if (pdata && pdata->mdio_bus_id >= 0) {
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx",
+ pdata->mdio_bus_id);
+ }
+
bus->priv = lp;
bus->name = "Xilinx TEMAC MDIO";
bus->read = temac_mdio_read;
diff --git a/include/linux/xilinx_ll_temac.h b/include/linux/xilinx_ll_temac.h
new file mode 100644
index 0000000..44d7dd6
--- /dev/null
+++ b/include/linux/xilinx_ll_temac.h
@@ -0,0 +1,18 @@
+#ifndef __LINUX_XILINX_LL_TEMAC_H
+#define __LINUX_XILINX_LL_TEMAC_H
+
+#include <linux/if_ether.h>
+#include <linux/phy.h>
+
+struct ll_temac_platform_data {
+ bool txcsum; /* Enable/disable TX checksum */
+ bool rxcsum; /* Enable/disable RX checksum */
+ u8 mac_addr[ETH_ALEN]; /* MAC address (6 bytes) */
+ /* Clock frequency for input to MDIO clock generator */
+ u32 mdio_clk_freq;
+ unsigned long long mdio_bus_id; /* Unique id for MDIO bus */
+ int phy_addr; /* Address of the PHY to connect to */
+ phy_interface_t phy_interface; /* PHY interface mode */
+};
+
+#endif /* __LINUX_XILINX_LL_TEMAC_H */
--
2.4.11

2019-04-26 07:42:12

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 10/12] net: ll_temac: Replace bad usage of msleep() with usleep_range()

Use usleep_range() to avoid problems with msleep() actually sleeping
much longer than expected.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac_main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index def3886..a5ec8ba 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -92,7 +92,7 @@ int temac_indirect_busywait(struct temac_local *lp)
WARN_ON(1);
return -ETIMEDOUT;
}
- msleep(1);
+ usleep_range(500, 1000);
}
return 0;
}
--
2.4.11

2019-04-26 07:42:29

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 09/12] net: ll_temac: Fix bug causing buffer descriptor overrun

As we are actually using a BD for both the skb and each frag contained in
it, the oldest TX BD would be overwritten when there was exactly one BD
less than needed.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac_main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 56d8077..def3886 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -745,7 +745,7 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
start_p = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * lp->tx_bd_tail;
cur_p = &lp->tx_bd_v[lp->tx_bd_tail];

- if (temac_check_tx_bd_space(lp, num_frag)) {
+ if (temac_check_tx_bd_space(lp, num_frag + 1)) {
if (!netif_queue_stopped(ndev))
netif_stop_queue(ndev);
return NETDEV_TX_BUSY;
--
2.4.11

2019-04-26 07:42:37

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 03/12] net: ll_temac: Fix support for 64-bit platforms

The use of buffer descriptor APP4 field (32-bit) for storing skb pointer
obviously does not work on 64-bit platforms.
As APP3 is also unused, we can use that to store the other half of 64-bit
pointer values.

Contrary to what is hinted at in commit message of commit 15bfe05c8d63
("net: ethernet: xilinx: Mark XILINX_LL_TEMAC broken on 64-bit")
there are no other pointers stored in cdmac_bd.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/Kconfig | 1 -
drivers/net/ethernet/xilinx/ll_temac_main.c | 35 ++++++++++++++++++++++++++---
2 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig
index da4ec57..6d68c8a 100644
--- a/drivers/net/ethernet/xilinx/Kconfig
+++ b/drivers/net/ethernet/xilinx/Kconfig
@@ -34,7 +34,6 @@ config XILINX_AXI_EMAC
config XILINX_LL_TEMAC
tristate "Xilinx LL TEMAC (LocalLink Tri-mode Ethernet MAC) driver"
depends on (PPC || MICROBLAZE)
- depends on !64BIT || BROKEN
select PHYLIB
---help---
This driver supports the Xilinx 10/100/1000 LocalLink TEMAC
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 4594fe3..a365bfd 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -619,11 +619,39 @@ static void temac_adjust_link(struct net_device *ndev)
mutex_unlock(&lp->indirect_mutex);
}

+#ifdef CONFIG_64BIT
+
+void ptr_to_txbd(void *p, struct cdmac_bd *bd)
+{
+ bd->app3 = (u32)(((u64)p) >> 32);
+ bd->app4 = (u32)((u64)p & 0xFFFFFFFF);
+}
+
+void *ptr_from_txbd(struct cdmac_bd *bd)
+{
+ return (void *)(((u64)(bd->app3) << 32) | bd->app4);
+}
+
+#else
+
+void ptr_to_txbd(void *p, struct cmdac_bd *bd)
+{
+ bd->app4 = (u32)p;
+}
+
+void *ptr_from_txbd(struct cdmac_bd *bd)
+{
+ return (void *)(bd->app4);
+}
+
+#endif
+
static void temac_start_xmit_done(struct net_device *ndev)
{
struct temac_local *lp = netdev_priv(ndev);
struct cdmac_bd *cur_p;
unsigned int stat = 0;
+ struct sk_buff *skb;

cur_p = &lp->tx_bd_v[lp->tx_bd_ci];
stat = cur_p->app0;
@@ -631,8 +659,9 @@ static void temac_start_xmit_done(struct net_device *ndev)
while (stat & STS_CTRL_APP0_CMPLT) {
dma_unmap_single(ndev->dev.parent, cur_p->phys, cur_p->len,
DMA_TO_DEVICE);
- if (cur_p->app4)
- dev_consume_skb_irq((struct sk_buff *)cur_p->app4);
+ skb = (struct sk_buff *)ptr_from_txbd(cur_p);
+ if (skb)
+ dev_consume_skb_irq(skb);
cur_p->app0 = 0;
cur_p->app1 = 0;
cur_p->app2 = 0;
@@ -711,7 +740,7 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
cur_p->len = skb_headlen(skb);
cur_p->phys = dma_map_single(ndev->dev.parent, skb->data,
skb_headlen(skb), DMA_TO_DEVICE);
- cur_p->app4 = (unsigned long)skb;
+ ptr_to_txbd((void *)skb, cur_p);

for (ii = 0; ii < num_frag; ii++) {
lp->tx_bd_tail++;
--
2.4.11

2019-04-26 07:42:51

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 08/12] net: ll_temac: Fix iommu/swiotlb leak

Unmap the actual buffer length, not the amount of data received.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac_main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 309f149..56d8077 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -821,7 +821,7 @@ static void ll_temac_recv(struct net_device *ndev)
length = be32_to_cpu(cur_p->app4) & 0x3FFF;

dma_unmap_single(ndev->dev.parent, be32_to_cpu(cur_p->phys),
- length, DMA_FROM_DEVICE);
+ XTE_MAX_JUMBO_FRAME_SIZE, DMA_FROM_DEVICE);

skb_put(skb, length);
skb->protocol = eth_type_trans(skb, ndev);
--
2.4.11

2019-04-26 07:43:53

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 01/12] net: ll_temac: Fix and simplify error handling by using devres functions

As a side effect, a few error cases are fixed.

If of_iomap() of sdma_regs failed, no error code was returned. Fixed to
return -ENOMEM similar to of_iomap() fail of regs.

If sysfs_create_group() or register_netdev() failed, lp->phy_node was not
released.

Finally, the order in remove function is corrected to be reverse order
of what is done in probe, i.e. calling temac_mdio_teardown() last, so we
unregister the netdev that most likely is using the mdio_bus first.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac.h | 2 +-
drivers/net/ethernet/xilinx/ll_temac_main.c | 48 ++++++++++-------------------
drivers/net/ethernet/xilinx/ll_temac_mdio.c | 14 +++------
3 files changed, 22 insertions(+), 42 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h
index 1075752..4557578 100644
--- a/drivers/net/ethernet/xilinx/ll_temac.h
+++ b/drivers/net/ethernet/xilinx/ll_temac.h
@@ -378,7 +378,7 @@ void temac_indirect_out32(struct temac_local *lp, int reg, u32 value);


/* xilinx_temac_mdio.c */
-int temac_mdio_setup(struct temac_local *lp, struct device_node *np);
+int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev);
void temac_mdio_teardown(struct temac_local *lp);

#endif /* XILINX_LL_TEMAC_H */
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 44efffb..c4e85a9 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -225,7 +225,6 @@ static void temac_dma_bd_release(struct net_device *ndev)
dma_free_coherent(ndev->dev.parent,
sizeof(*lp->tx_bd_v) * TX_BD_NUM,
lp->tx_bd_v, lp->tx_bd_p);
- kfree(lp->rx_skb);
}

/**
@@ -237,7 +236,8 @@ static int temac_dma_bd_init(struct net_device *ndev)
struct sk_buff *skb;
int i;

- lp->rx_skb = kcalloc(RX_BD_NUM, sizeof(*lp->rx_skb), GFP_KERNEL);
+ lp->rx_skb = devm_kcalloc(&ndev->dev, RX_BD_NUM, sizeof(*lp->rx_skb),
+ GFP_KERNEL);
if (!lp->rx_skb)
goto out;

@@ -987,7 +987,7 @@ static int temac_of_probe(struct platform_device *op)
int rc = 0;

/* Init network device structure */
- ndev = alloc_etherdev(sizeof(*lp));
+ ndev = devm_alloc_etherdev(&pdev->dev, sizeof(*lp));
if (!ndev)
return -ENOMEM;

@@ -1020,11 +1020,10 @@ static int temac_of_probe(struct platform_device *op)
mutex_init(&lp->indirect_mutex);

/* map device registers */
- lp->regs = of_iomap(op->dev.of_node, 0);
+ lp->regs = devm_of_iomap(&op->dev, op->dev.of_node, 0, NULL);
if (!lp->regs) {
dev_err(&op->dev, "could not map temac regs.\n");
- rc = -ENOMEM;
- goto nodev;
+ return -ENOMEM;
}

/* Setup checksum offload, but default to off if not specified */
@@ -1043,15 +1042,14 @@ static int temac_of_probe(struct platform_device *op)
np = of_parse_phandle(op->dev.of_node, "llink-connected", 0);
if (!np) {
dev_err(&op->dev, "could not find DMA node\n");
- rc = -ENODEV;
- goto err_iounmap;
+ return -ENODEV;
}

/* Setup the DMA register accesses, could be DCR or memory mapped */
if (temac_dcr_setup(lp, op, np)) {

/* no DCR in the device tree, try non-DCR */
- lp->sdma_regs = of_iomap(np, 0);
+ lp->sdma_regs = devm_of_iomap(&op->dev, np, 0, NULL);
if (lp->sdma_regs) {
lp->dma_in = temac_dma_in32;
lp->dma_out = temac_dma_out32;
@@ -1059,7 +1057,7 @@ static int temac_of_probe(struct platform_device *op)
} else {
dev_err(&op->dev, "unable to map DMA registers\n");
of_node_put(np);
- goto err_iounmap;
+ return -ENOMEM;
}
}

@@ -1070,8 +1068,7 @@ static int temac_of_probe(struct platform_device *op)

if (!lp->rx_irq || !lp->tx_irq) {
dev_err(&op->dev, "could not determine irqs\n");
- rc = -ENOMEM;
- goto err_iounmap_2;
+ return -ENOMEM;
}


@@ -1079,12 +1076,11 @@ static int temac_of_probe(struct platform_device *op)
addr = of_get_mac_address(op->dev.of_node);
if (!addr) {
dev_err(&op->dev, "could not find MAC address\n");
- rc = -ENODEV;
- goto err_iounmap_2;
+ return -ENODEV;
}
temac_init_mac_address(ndev, addr);

- rc = temac_mdio_setup(lp, op->dev.of_node);
+ rc = temac_mdio_setup(lp, pdev);
if (rc)
dev_warn(&op->dev, "error registering MDIO bus\n");

@@ -1096,7 +1092,7 @@ static int temac_of_probe(struct platform_device *op)
rc = sysfs_create_group(&lp->dev->kobj, &temac_attr_group);
if (rc) {
dev_err(lp->dev, "Error creating sysfs files\n");
- goto err_iounmap_2;
+ goto err_sysfs_create;
}

rc = register_netdev(lp->ndev);
@@ -1107,16 +1103,11 @@ static int temac_of_probe(struct platform_device *op)

return 0;

- err_register_ndev:
+err_register_ndev:
sysfs_remove_group(&lp->dev->kobj, &temac_attr_group);
- err_iounmap_2:
- if (lp->sdma_regs)
- iounmap(lp->sdma_regs);
- err_iounmap:
- iounmap(lp->regs);
- nodev:
- free_netdev(ndev);
- ndev = NULL;
+err_sysfs_create:
+ of_node_put(lp->phy_node);
+ temac_mdio_teardown(lp);
return rc;
}

@@ -1125,15 +1116,10 @@ static int temac_of_remove(struct platform_device *op)
struct net_device *ndev = platform_get_drvdata(op);
struct temac_local *lp = netdev_priv(ndev);

- temac_mdio_teardown(lp);
unregister_netdev(ndev);
sysfs_remove_group(&lp->dev->kobj, &temac_attr_group);
of_node_put(lp->phy_node);
- lp->phy_node = NULL;
- iounmap(lp->regs);
- if (lp->sdma_regs)
- iounmap(lp->sdma_regs);
- free_netdev(ndev);
+ temac_mdio_teardown(lp);
return 0;
}

diff --git a/drivers/net/ethernet/xilinx/ll_temac_mdio.c b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
index f5e83ac..a0b365e 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_mdio.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
@@ -57,8 +57,9 @@ static int temac_mdio_write(struct mii_bus *bus, int phy_id, int reg, u16 val)
return 0;
}

-int temac_mdio_setup(struct temac_local *lp, struct device_node *np)
+int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)
{
+ struct device_node *np = dev_of_node(&pdev->dev);
struct mii_bus *bus;
u32 bus_hz;
int clk_div;
@@ -81,7 +82,7 @@ int temac_mdio_setup(struct temac_local *lp, struct device_node *np)
temac_indirect_out32(lp, XTE_MC_OFFSET, 1 << 6 | clk_div);
mutex_unlock(&lp->indirect_mutex);

- bus = mdiobus_alloc();
+ bus = devm_mdiobus_alloc(&pdev->dev);
if (!bus)
return -ENOMEM;

@@ -98,23 +99,16 @@ int temac_mdio_setup(struct temac_local *lp, struct device_node *np)

rc = of_mdiobus_register(bus, np);
if (rc)
- goto err_register;
+ return rc;

mutex_lock(&lp->indirect_mutex);
dev_dbg(lp->dev, "MDIO bus registered; MC:%x\n",
temac_indirect_in32(lp, XTE_MC_OFFSET));
mutex_unlock(&lp->indirect_mutex);
return 0;
-
- err_register:
- mdiobus_free(bus);
- return rc;
}

void temac_mdio_teardown(struct temac_local *lp)
{
mdiobus_unregister(lp->mii_bus);
- mdiobus_free(lp->mii_bus);
- lp->mii_bus = NULL;
}
-
--
2.4.11

2019-04-26 07:44:11

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 06/12] net: ll_temac: Allow use on x86 platforms

With little-endian and 64-bit support in place, the ll_temac driver can
now be used on x86 and x86_64 platforms.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/Kconfig | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig
index 6d68c8a..6f858f6 100644
--- a/drivers/net/ethernet/xilinx/Kconfig
+++ b/drivers/net/ethernet/xilinx/Kconfig
@@ -5,7 +5,7 @@
config NET_VENDOR_XILINX
bool "Xilinx devices"
default y
- depends on PPC || PPC32 || MICROBLAZE || ARCH_ZYNQ || MIPS
+ depends on PPC || PPC32 || MICROBLAZE || ARCH_ZYNQ || MIPS || X86
---help---
If you have a network (Ethernet) card belonging to this class, say Y.

@@ -33,7 +33,7 @@ config XILINX_AXI_EMAC

config XILINX_LL_TEMAC
tristate "Xilinx LL TEMAC (LocalLink Tri-mode Ethernet MAC) driver"
- depends on (PPC || MICROBLAZE)
+ depends on PPC || MICROBLAZE || X86
select PHYLIB
---help---
This driver supports the Xilinx 10/100/1000 LocalLink TEMAC
--
2.4.11

2019-04-26 07:44:18

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 12/12] net: ll_temac: Enable DMA when ready, not before

As soon as TAILDESCR_PTR is written, DMA transfers might start.
Let's ensure we are ready to receive DMA IRQ's before doing that.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac_main.c | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index a30ac11..b8e3dbd 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -316,17 +316,21 @@ static int temac_dma_bd_init(struct net_device *ndev)
CHNL_CTRL_IRQ_EN | CHNL_CTRL_IRQ_ERR_EN |
CHNL_CTRL_IRQ_DLY_EN | CHNL_CTRL_IRQ_COAL_EN);

- lp->dma_out(lp, RX_CURDESC_PTR, lp->rx_bd_p);
- lp->dma_out(lp, RX_TAILDESC_PTR,
- lp->rx_bd_p + (sizeof(*lp->rx_bd_v) * (RX_BD_NUM - 1)));
- lp->dma_out(lp, TX_CURDESC_PTR, lp->tx_bd_p);
-
/* Init descriptor indexes */
lp->tx_bd_ci = 0;
lp->tx_bd_next = 0;
lp->tx_bd_tail = 0;
lp->rx_bd_ci = 0;

+ /* Enable RX DMA transfers */
+ wmb();
+ lp->dma_out(lp, RX_CURDESC_PTR, lp->rx_bd_p);
+ lp->dma_out(lp, RX_TAILDESC_PTR,
+ lp->rx_bd_p + (sizeof(*lp->rx_bd_v) * (RX_BD_NUM - 1)));
+
+ /* Prepare for TX DMA transfer */
+ lp->dma_out(lp, TX_CURDESC_PTR, lp->tx_bd_p);
+
return 0;

out:
@@ -790,6 +794,7 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
skb_tx_timestamp(skb);

/* Kick off the transfer */
+ wmb();
lp->dma_out(lp, TX_TAILDESC_PTR, tail_p); /* DMA start */

return NETDEV_TX_OK;
--
2.4.11

2019-04-26 14:00:04

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH 02/12] net: ll_temac: Extend support to non-device-tree platforms

On Fri, Apr 26, 2019 at 09:32:21AM +0200, Esben Haabendal wrote:
> Support initialization with platdata, so the driver can be used on
> non-device-tree platforms.
>
> For currently supported device-tree platforms, the driver should behave
> as before.
>
> Signed-off-by: Esben Haabendal <[email protected]>
> ---
> drivers/net/ethernet/xilinx/ll_temac.h | 3 +
> drivers/net/ethernet/xilinx/ll_temac_main.c | 187 +++++++++++++++++++---------
> drivers/net/ethernet/xilinx/ll_temac_mdio.c | 23 +++-
> include/linux/xilinx_ll_temac.h | 18 +++

Hi Esben

Please place platform data include files in include/linux/platform_data/

Thanks
Andrew

2019-04-26 14:03:30

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH 10/12] net: ll_temac: Replace bad usage of msleep() with usleep_range()

On Fri, Apr 26, 2019 at 09:32:29AM +0200, Esben Haabendal wrote:
> Use usleep_range() to avoid problems with msleep() actually sleeping
> much longer than expected.
>
> Signed-off-by: Esben Haabendal <[email protected]>

Reviewed-by: Andrew Lunn <[email protected]>

Andrew

2019-04-26 14:07:20

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH 06/12] net: ll_temac: Allow use on x86 platforms

On Fri, Apr 26, 2019 at 09:32:25AM +0200, Esben Haabendal wrote:
> With little-endian and 64-bit support in place, the ll_temac driver can
> now be used on x86 and x86_64 platforms.
>
> Signed-off-by: Esben Haabendal <[email protected]>
> ---
> drivers/net/ethernet/xilinx/Kconfig | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig
> index 6d68c8a..6f858f6 100644
> --- a/drivers/net/ethernet/xilinx/Kconfig
> +++ b/drivers/net/ethernet/xilinx/Kconfig
> @@ -5,7 +5,7 @@
> config NET_VENDOR_XILINX
> bool "Xilinx devices"
> default y
> - depends on PPC || PPC32 || MICROBLAZE || ARCH_ZYNQ || MIPS
> + depends on PPC || PPC32 || MICROBLAZE || ARCH_ZYNQ || MIPS || X86

Hi Esben

While you are here, maybe also add COMPILE_TEST here and to
XILINX_LL_TEMAC?

Andrew

2019-04-26 14:16:22

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH 07/12] net: ll_temac: Support indirect_mutex share within TEMAC IP

On Fri, Apr 26, 2019 at 09:32:26AM +0200, Esben Haabendal wrote:
> @@ -1092,7 +1092,15 @@ static int temac_probe(struct platform_device *pdev)
> lp->dev = &pdev->dev;
> lp->options = XTE_OPTION_DEFAULTS;
> spin_lock_init(&lp->rx_lock);
> - mutex_init(&lp->indirect_mutex);
> +
> + /* Setup mutex for synchronization of indirect register access */
> + if (pdata && pdata->indirect_mutex) {
> + lp->indirect_mutex = pdata->indirect_mutex;
> + } else {
> + lp->indirect_mutex = devm_kmalloc(
> + &pdev->dev, sizeof(*lp->indirect_mutex), GFP_KERNEL);
> + mutex_init(lp->indirect_mutex);
> + }

Hi Esben

I would make the mutex mandatory, not optional. I think there will be
less hard to debug errors that way. You want the developer to actually
think about this mutex, should it be shared, or individual. Forcing
them to provide it means they are more likely to read the
documentation, and more likely to over share it than under share
it. That is maybe not so good for performance, but safer.

Andrew

2019-04-26 14:22:29

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH 08/12] net: ll_temac: Fix iommu/swiotlb leak

On Fri, Apr 26, 2019 at 09:32:27AM +0200, Esben Haabendal wrote:
> Unmap the actual buffer length, not the amount of data received.

Hi Esben

The patch Subject does not seem to match the content?

Also, there can be performance advantages of just unmapping the
received length. The unmap operation does a cache invalidate, which
can be expensive. Consider the effort of unmapping a 64 byte ACK vs 9K
jumbo frame?

Andrew

> Signed-off-by: Esben Haabendal <[email protected]>
> ---
> drivers/net/ethernet/xilinx/ll_temac_main.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
> index 309f149..56d8077 100644
> --- a/drivers/net/ethernet/xilinx/ll_temac_main.c
> +++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
> @@ -821,7 +821,7 @@ static void ll_temac_recv(struct net_device *ndev)
> length = be32_to_cpu(cur_p->app4) & 0x3FFF;
>
> dma_unmap_single(ndev->dev.parent, be32_to_cpu(cur_p->phys),
> - length, DMA_FROM_DEVICE);
> + XTE_MAX_JUMBO_FRAME_SIZE, DMA_FROM_DEVICE);
>
> skb_put(skb, length);
> skb->protocol = eth_type_trans(skb, ndev);
> --
> 2.4.11
>

2019-04-26 14:44:38

by Robin Murphy

[permalink] [raw]
Subject: Re: [PATCH 08/12] net: ll_temac: Fix iommu/swiotlb leak

On 26/04/2019 15:21, Andrew Lunn wrote:
> On Fri, Apr 26, 2019 at 09:32:27AM +0200, Esben Haabendal wrote:
>> Unmap the actual buffer length, not the amount of data received.
>
> Hi Esben
>
> The patch Subject does not seem to match the content?
>
> Also, there can be performance advantages of just unmapping the
> received length. The unmap operation does a cache invalidate, which
> can be expensive. Consider the effort of unmapping a 64 byte ACK vs 9K
> jumbo frame?

If the size passed to dma_unmap_*() is not the same as was passed to the
corresponding dma_map_*(), that is fundamentally incorrect use of the
API and may lead to warnings, resource exhaustion, or possibly even
corruption and crashes for some DMA API implementations.

If there's a case where you just need to look at a small part of the
buffer right now, but can unmap the whole thing properly later. then
dma_sync_single_*() does allow operating on partial buffers. Even
better, if you're able to recycle buffers in your Rx pool you could
potentially replace the unmap/map dance altogether with some careful use
of sync_single.

Robin.

>
> Andrew
>
>> Signed-off-by: Esben Haabendal <[email protected]>
>> ---
>> drivers/net/ethernet/xilinx/ll_temac_main.c | 2 +-
>> 1 file changed, 1 insertion(+), 1 deletion(-)
>>
>> diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
>> index 309f149..56d8077 100644
>> --- a/drivers/net/ethernet/xilinx/ll_temac_main.c
>> +++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
>> @@ -821,7 +821,7 @@ static void ll_temac_recv(struct net_device *ndev)
>> length = be32_to_cpu(cur_p->app4) & 0x3FFF;
>>
>> dma_unmap_single(ndev->dev.parent, be32_to_cpu(cur_p->phys),
>> - length, DMA_FROM_DEVICE);
>> + XTE_MAX_JUMBO_FRAME_SIZE, DMA_FROM_DEVICE);
>>
>> skb_put(skb, length);
>> skb->protocol = eth_type_trans(skb, ndev);
>> --
>> 2.4.11
>>
>
> _______________________________________________
> linux-arm-kernel mailing list
> [email protected]
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>

2019-04-26 15:38:54

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH 08/12] net: ll_temac: Fix iommu/swiotlb leak

On Fri, Apr 26, 2019 at 03:43:20PM +0100, Robin Murphy wrote:
> On 26/04/2019 15:21, Andrew Lunn wrote:
> >On Fri, Apr 26, 2019 at 09:32:27AM +0200, Esben Haabendal wrote:
> >>Unmap the actual buffer length, not the amount of data received.
> >
> >Hi Esben
> >
> >The patch Subject does not seem to match the content?
> >
> >Also, there can be performance advantages of just unmapping the
> >received length. The unmap operation does a cache invalidate, which
> >can be expensive. Consider the effort of unmapping a 64 byte ACK vs 9K
> >jumbo frame?
>
> If the size passed to dma_unmap_*() is not the same as was passed to the
> corresponding dma_map_*(), that is fundamentally incorrect use of the API
> and may lead to warnings, resource exhaustion, or possibly even corruption
> and crashes for some DMA API implementations.
>
> If there's a case where you just need to look at a small part of the buffer
> right now, but can unmap the whole thing properly later. then
> dma_sync_single_*() does allow operating on partial buffers. Even better, if
> you're able to recycle buffers in your Rx pool you could potentially replace
> the unmap/map dance altogether with some careful use of sync_single.

Hi Robin

Thanks for the info.

I went back to the driver i was thinking of, and it is using
dma_sync_single_range_for_cpu() for just the received packet length.

Sorry for the mixup.

Andrew

2019-04-26 18:42:26

by Jakub Kicinski

[permalink] [raw]
Subject: Re: [PATCH 03/12] net: ll_temac: Fix support for 64-bit platforms

On Fri, 26 Apr 2019 09:32:22 +0200, Esben Haabendal wrote:
> The use of buffer descriptor APP4 field (32-bit) for storing skb pointer
> obviously does not work on 64-bit platforms.
> As APP3 is also unused, we can use that to store the other half of 64-bit
> pointer values.
>
> Contrary to what is hinted at in commit message of commit 15bfe05c8d63
> ("net: ethernet: xilinx: Mark XILINX_LL_TEMAC broken on 64-bit")
> there are no other pointers stored in cdmac_bd.
>
> Signed-off-by: Esben Haabendal <[email protected]>

This is a bit strange, the driver stores the host's virtual address into
the HW descriptor?

> diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig
> index da4ec57..6d68c8a 100644
> --- a/drivers/net/ethernet/xilinx/Kconfig
> +++ b/drivers/net/ethernet/xilinx/Kconfig
> @@ -34,7 +34,6 @@ config XILINX_AXI_EMAC
> config XILINX_LL_TEMAC
> tristate "Xilinx LL TEMAC (LocalLink Tri-mode Ethernet MAC) driver"
> depends on (PPC || MICROBLAZE)
> - depends on !64BIT || BROKEN
> select PHYLIB
> ---help---
> This driver supports the Xilinx 10/100/1000 LocalLink TEMAC
> diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
> index 4594fe3..a365bfd 100644
> --- a/drivers/net/ethernet/xilinx/ll_temac_main.c
> +++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
> @@ -619,11 +619,39 @@ static void temac_adjust_link(struct net_device *ndev)
> mutex_unlock(&lp->indirect_mutex);
> }
>
> +#ifdef CONFIG_64BIT
> +
> +void ptr_to_txbd(void *p, struct cdmac_bd *bd)
> +{
> + bd->app3 = (u32)(((u64)p) >> 32);
> + bd->app4 = (u32)((u64)p & 0xFFFFFFFF);
> +}
> +
> +void *ptr_from_txbd(struct cdmac_bd *bd)
> +{
> + return (void *)(((u64)(bd->app3) << 32) | bd->app4);
> +}
> +
> +#else
> +
> +void ptr_to_txbd(void *p, struct cmdac_bd *bd)
> +{
> + bd->app4 = (u32)p;
> +}
> +
> +void *ptr_from_txbd(struct cdmac_bd *bd)
> +{
> + return (void *)(bd->app4);
> +}
> +
> +#endif
> +
> static void temac_start_xmit_done(struct net_device *ndev)
> {
> struct temac_local *lp = netdev_priv(ndev);
> struct cdmac_bd *cur_p;
> unsigned int stat = 0;
> + struct sk_buff *skb;
>
> cur_p = &lp->tx_bd_v[lp->tx_bd_ci];
> stat = cur_p->app0;
> @@ -631,8 +659,9 @@ static void temac_start_xmit_done(struct net_device *ndev)
> while (stat & STS_CTRL_APP0_CMPLT) {
> dma_unmap_single(ndev->dev.parent, cur_p->phys, cur_p->len,
> DMA_TO_DEVICE);
> - if (cur_p->app4)
> - dev_consume_skb_irq((struct sk_buff *)cur_p->app4);
> + skb = (struct sk_buff *)ptr_from_txbd(cur_p);
> + if (skb)
> + dev_consume_skb_irq(skb);
> cur_p->app0 = 0;
> cur_p->app1 = 0;
> cur_p->app2 = 0;
> @@ -711,7 +740,7 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> cur_p->len = skb_headlen(skb);
> cur_p->phys = dma_map_single(ndev->dev.parent, skb->data,
> skb_headlen(skb), DMA_TO_DEVICE);
> - cur_p->app4 = (unsigned long)skb;
> + ptr_to_txbd((void *)skb, cur_p);
>
> for (ii = 0; ii < num_frag; ii++) {
> lp->tx_bd_tail++;

2019-04-26 21:00:49

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH 03/12] net: ll_temac: Fix support for 64-bit platforms

On Fri, Apr 26, 2019 at 11:40:13AM -0700, Jakub Kicinski wrote:
> On Fri, 26 Apr 2019 09:32:22 +0200, Esben Haabendal wrote:
> > The use of buffer descriptor APP4 field (32-bit) for storing skb pointer
> > obviously does not work on 64-bit platforms.
> > As APP3 is also unused, we can use that to store the other half of 64-bit
> > pointer values.
> >
> > Contrary to what is hinted at in commit message of commit 15bfe05c8d63
> > ("net: ethernet: xilinx: Mark XILINX_LL_TEMAC broken on 64-bit")
> > there are no other pointers stored in cdmac_bd.
> >
> > Signed-off-by: Esben Haabendal <[email protected]>
>
> This is a bit strange, the driver stores the host's virtual address into
> the HW descriptor?

Hi Jukub

This is reasonably common. You need some sort of cookie which links
the hardware descriptor to the skbuf it points to. The hardware makes
no use of it, it is just a cookie.

Andrew

2019-04-26 21:10:13

by Jakub Kicinski

[permalink] [raw]
Subject: Re: [PATCH 03/12] net: ll_temac: Fix support for 64-bit platforms

On Fri, 26 Apr 2019 22:59:12 +0200, Andrew Lunn wrote:
> On Fri, Apr 26, 2019 at 11:40:13AM -0700, Jakub Kicinski wrote:
> > On Fri, 26 Apr 2019 09:32:22 +0200, Esben Haabendal wrote:
> > > The use of buffer descriptor APP4 field (32-bit) for storing skb pointer
> > > obviously does not work on 64-bit platforms.
> > > As APP3 is also unused, we can use that to store the other half of 64-bit
> > > pointer values.
> > >
> > > Contrary to what is hinted at in commit message of commit 15bfe05c8d63
> > > ("net: ethernet: xilinx: Mark XILINX_LL_TEMAC broken on 64-bit")
> > > there are no other pointers stored in cdmac_bd.
> > >
> > > Signed-off-by: Esben Haabendal <[email protected]>
> >
> > This is a bit strange, the driver stores the host's virtual address into
> > the HW descriptor?
>
> Hi Jukub

I need to start keeping track of all the ways my name gets spelled :)
I find it entertaining :)

> This is reasonably common. You need some sort of cookie which links
> the hardware descriptor to the skbuf it points to. The hardware makes
> no use of it, it is just a cookie.

Right, but accesses to HW descriptor memory ring are significantly
more expensive, especially on platforms which are not coherent with
DMA operations (everything but x86?)

A preferable design is to have two descriptor rings - one for HW
descriptors and one for software context, no?

2019-04-26 22:03:59

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH 03/12] net: ll_temac: Fix support for 64-bit platforms

On Fri, Apr 26, 2019 at 02:08:56PM -0700, Jakub Kicinski wrote:
> On Fri, 26 Apr 2019 22:59:12 +0200, Andrew Lunn wrote:
> > On Fri, Apr 26, 2019 at 11:40:13AM -0700, Jakub Kicinski wrote:
> > > On Fri, 26 Apr 2019 09:32:22 +0200, Esben Haabendal wrote:
> > > > The use of buffer descriptor APP4 field (32-bit) for storing skb pointer
> > > > obviously does not work on 64-bit platforms.
> > > > As APP3 is also unused, we can use that to store the other half of 64-bit
> > > > pointer values.
> > > >
> > > > Contrary to what is hinted at in commit message of commit 15bfe05c8d63
> > > > ("net: ethernet: xilinx: Mark XILINX_LL_TEMAC broken on 64-bit")
> > > > there are no other pointers stored in cdmac_bd.
> > > >
> > > > Signed-off-by: Esben Haabendal <[email protected]>
> > >
> > > This is a bit strange, the driver stores the host's virtual address into
> > > the HW descriptor?

Lets try that again

Hi Jakub

> > Hi Jukub
>
> I need to start keeping track of all the ways my name gets spelled :)
> I find it entertaining :)

Sorry.

And i prefer entertaining over offended :-)

> > This is reasonably common. You need some sort of cookie which links
> > the hardware descriptor to the skbuf it points to. The hardware makes
> > no use of it, it is just a cookie.
>
> Right, but accesses to HW descriptor memory ring are significantly
> more expensive, especially on platforms which are not coherent with
> DMA operations (everything but x86?)
>
> A preferable design is to have two descriptor rings - one for HW
> descriptors and one for software context, no?

Modern drivers do that. But this driver seems to be quite old. And if
you look at what it is used on, PPC & MICROBLAZE, they are old
architectures, i don't think hardware access are that as expensive as
for modern architectures.

Andrew

2019-04-26 22:32:11

by Jakub Kicinski

[permalink] [raw]
Subject: Re: [PATCH 03/12] net: ll_temac: Fix support for 64-bit platforms

On Sat, 27 Apr 2019 00:02:26 +0200, Andrew Lunn wrote:
> On Fri, Apr 26, 2019 at 02:08:56PM -0700, Jakub Kicinski wrote:
> > On Fri, 26 Apr 2019 22:59:12 +0200, Andrew Lunn wrote:
> > > On Fri, Apr 26, 2019 at 11:40:13AM -0700, Jakub Kicinski wrote:
> > > > On Fri, 26 Apr 2019 09:32:22 +0200, Esben Haabendal wrote:
> > > > > The use of buffer descriptor APP4 field (32-bit) for storing skb pointer
> > > > > obviously does not work on 64-bit platforms.
> > > > > As APP3 is also unused, we can use that to store the other half of 64-bit
> > > > > pointer values.
> > > > >
> > > > > Contrary to what is hinted at in commit message of commit 15bfe05c8d63
> > > > > ("net: ethernet: xilinx: Mark XILINX_LL_TEMAC broken on 64-bit")
> > > > > there are no other pointers stored in cdmac_bd.
> > > > >
> > > > > Signed-off-by: Esben Haabendal <[email protected]>
> > > >
> > > > This is a bit strange, the driver stores the host's virtual address into
> > > > the HW descriptor?
>
> Lets try that again
>
> Hi Jakub

:)

> > > Hi Jukub
> >
> > I need to start keeping track of all the ways my name gets spelled :)
> > I find it entertaining :)
>
> Sorry.
>
> And i prefer entertaining over offended :-)

Certainly no offence taken! :)

> > > This is reasonably common. You need some sort of cookie which links
> > > the hardware descriptor to the skbuf it points to. The hardware makes
> > > no use of it, it is just a cookie.
> >
> > Right, but accesses to HW descriptor memory ring are significantly
> > more expensive, especially on platforms which are not coherent with
> > DMA operations (everything but x86?)
> >
> > A preferable design is to have two descriptor rings - one for HW
> > descriptors and one for software context, no?
>
> Modern drivers do that. But this driver seems to be quite old. And if
> you look at what it is used on, PPC & MICROBLAZE, they are old
> architectures, i don't think hardware access are that as expensive as
> for modern architectures.

True, my comment was certainly more of a suggestion than a blocker.

Looking closer at the series it kind of looks like a soft IP.
Esben, is there anything architecture specific here? Should we perhaps
drop the dependency on the architectures in patch 6 completely?

2019-04-27 08:50:49

by Esben Haabendal

[permalink] [raw]
Subject: Re: [PATCH 03/12] net: ll_temac: Fix support for 64-bit platforms

Jakub Kicinski <[email protected]> writes:

> Looking closer at the series it kind of looks like a soft IP.

It is a soft IP.

> Esben, is there anything architecture specific here? Should we perhaps
> drop the dependency on the architectures in patch 6 completely?

No, I don't think there is anything really architecture specific here.
Anything with PCIe should be able to utilize the LL TEMAC soft IP and
use this driver.

I don't mind dropping the architecture dependencies.

/Esben

2019-04-29 08:35:38

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 00/12] net: ll_temac: x86_64 support

This patch series adds support for use of ll_temac driver with
platform_data configuration and fixes endianess and 64-bit problems so
that it can be used on x86_64 platform.

A few bugfixes are also included.

Changes since v1:

- Make indirect_mutex specification mandatory when using platform_data
- Move header to include/linux/platform_data
- Enable COMPILE_TEST for XILINX_LL_TEMAC
- Rebased to v5.1-rc7

Esben Haabendal (12):
net: ll_temac: Fix and simplify error handling by using devres
functions
net: ll_temac: Extend support to non-device-tree platforms
net: ll_temac: Fix support for 64-bit platforms
net: ll_temac: Add support for non-native register endianness
net: ll_temac: Fix support for little-endian platforms
net: ll_temac: Allow use on x86 platforms
net: ll_temac: Support indirect_mutex share within TEMAC IP
net: ll_temac: Fix iommu/swiotlb leak
net: ll_temac: Fix bug causing buffer descriptor overrun
net: ll_temac: Replace bad usage of msleep() with usleep_range()
net: ll_temac: Allow configuration of IRQ coalescing
net: ll_temac: Enable DMA when ready, not before

drivers/net/ethernet/xilinx/Kconfig | 5 +-
drivers/net/ethernet/xilinx/ll_temac.h | 26 +-
drivers/net/ethernet/xilinx/ll_temac_main.c | 515 +++++++++++++++++---------
drivers/net/ethernet/xilinx/ll_temac_mdio.c | 53 +--
include/linux/platform_data/xilinx-ll-temac.h | 32 ++
5 files changed, 428 insertions(+), 203 deletions(-)
create mode 100644 include/linux/platform_data/xilinx-ll-temac.h

--
2.4.11

2019-04-29 08:35:59

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 01/12] net: ll_temac: Fix and simplify error handling by using devres functions

As a side effect, a few error cases are fixed.

If of_iomap() of sdma_regs failed, no error code was returned. Fixed to
return -ENOMEM similar to of_iomap() fail of regs.

If sysfs_create_group() or register_netdev() failed, lp->phy_node was not
released.

Finally, the order in remove function is corrected to be reverse order
of what is done in probe, i.e. calling temac_mdio_teardown() last, so we
unregister the netdev that most likely is using the mdio_bus first.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac.h | 2 +-
drivers/net/ethernet/xilinx/ll_temac_main.c | 48 ++++++++++-------------------
drivers/net/ethernet/xilinx/ll_temac_mdio.c | 14 +++------
3 files changed, 22 insertions(+), 42 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h
index 1075752..4557578 100644
--- a/drivers/net/ethernet/xilinx/ll_temac.h
+++ b/drivers/net/ethernet/xilinx/ll_temac.h
@@ -378,7 +378,7 @@ void temac_indirect_out32(struct temac_local *lp, int reg, u32 value);


/* xilinx_temac_mdio.c */
-int temac_mdio_setup(struct temac_local *lp, struct device_node *np);
+int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev);
void temac_mdio_teardown(struct temac_local *lp);

#endif /* XILINX_LL_TEMAC_H */
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 44efffb..c4e85a9 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -225,7 +225,6 @@ static void temac_dma_bd_release(struct net_device *ndev)
dma_free_coherent(ndev->dev.parent,
sizeof(*lp->tx_bd_v) * TX_BD_NUM,
lp->tx_bd_v, lp->tx_bd_p);
- kfree(lp->rx_skb);
}

/**
@@ -237,7 +236,8 @@ static int temac_dma_bd_init(struct net_device *ndev)
struct sk_buff *skb;
int i;

- lp->rx_skb = kcalloc(RX_BD_NUM, sizeof(*lp->rx_skb), GFP_KERNEL);
+ lp->rx_skb = devm_kcalloc(&ndev->dev, RX_BD_NUM, sizeof(*lp->rx_skb),
+ GFP_KERNEL);
if (!lp->rx_skb)
goto out;

@@ -987,7 +987,7 @@ static int temac_of_probe(struct platform_device *op)
int rc = 0;

/* Init network device structure */
- ndev = alloc_etherdev(sizeof(*lp));
+ ndev = devm_alloc_etherdev(&pdev->dev, sizeof(*lp));
if (!ndev)
return -ENOMEM;

@@ -1020,11 +1020,10 @@ static int temac_of_probe(struct platform_device *op)
mutex_init(&lp->indirect_mutex);

/* map device registers */
- lp->regs = of_iomap(op->dev.of_node, 0);
+ lp->regs = devm_of_iomap(&op->dev, op->dev.of_node, 0, NULL);
if (!lp->regs) {
dev_err(&op->dev, "could not map temac regs.\n");
- rc = -ENOMEM;
- goto nodev;
+ return -ENOMEM;
}

/* Setup checksum offload, but default to off if not specified */
@@ -1043,15 +1042,14 @@ static int temac_of_probe(struct platform_device *op)
np = of_parse_phandle(op->dev.of_node, "llink-connected", 0);
if (!np) {
dev_err(&op->dev, "could not find DMA node\n");
- rc = -ENODEV;
- goto err_iounmap;
+ return -ENODEV;
}

/* Setup the DMA register accesses, could be DCR or memory mapped */
if (temac_dcr_setup(lp, op, np)) {

/* no DCR in the device tree, try non-DCR */
- lp->sdma_regs = of_iomap(np, 0);
+ lp->sdma_regs = devm_of_iomap(&op->dev, np, 0, NULL);
if (lp->sdma_regs) {
lp->dma_in = temac_dma_in32;
lp->dma_out = temac_dma_out32;
@@ -1059,7 +1057,7 @@ static int temac_of_probe(struct platform_device *op)
} else {
dev_err(&op->dev, "unable to map DMA registers\n");
of_node_put(np);
- goto err_iounmap;
+ return -ENOMEM;
}
}

@@ -1070,8 +1068,7 @@ static int temac_of_probe(struct platform_device *op)

if (!lp->rx_irq || !lp->tx_irq) {
dev_err(&op->dev, "could not determine irqs\n");
- rc = -ENOMEM;
- goto err_iounmap_2;
+ return -ENOMEM;
}


@@ -1079,12 +1076,11 @@ static int temac_of_probe(struct platform_device *op)
addr = of_get_mac_address(op->dev.of_node);
if (!addr) {
dev_err(&op->dev, "could not find MAC address\n");
- rc = -ENODEV;
- goto err_iounmap_2;
+ return -ENODEV;
}
temac_init_mac_address(ndev, addr);

- rc = temac_mdio_setup(lp, op->dev.of_node);
+ rc = temac_mdio_setup(lp, pdev);
if (rc)
dev_warn(&op->dev, "error registering MDIO bus\n");

@@ -1096,7 +1092,7 @@ static int temac_of_probe(struct platform_device *op)
rc = sysfs_create_group(&lp->dev->kobj, &temac_attr_group);
if (rc) {
dev_err(lp->dev, "Error creating sysfs files\n");
- goto err_iounmap_2;
+ goto err_sysfs_create;
}

rc = register_netdev(lp->ndev);
@@ -1107,16 +1103,11 @@ static int temac_of_probe(struct platform_device *op)

return 0;

- err_register_ndev:
+err_register_ndev:
sysfs_remove_group(&lp->dev->kobj, &temac_attr_group);
- err_iounmap_2:
- if (lp->sdma_regs)
- iounmap(lp->sdma_regs);
- err_iounmap:
- iounmap(lp->regs);
- nodev:
- free_netdev(ndev);
- ndev = NULL;
+err_sysfs_create:
+ of_node_put(lp->phy_node);
+ temac_mdio_teardown(lp);
return rc;
}

@@ -1125,15 +1116,10 @@ static int temac_of_remove(struct platform_device *op)
struct net_device *ndev = platform_get_drvdata(op);
struct temac_local *lp = netdev_priv(ndev);

- temac_mdio_teardown(lp);
unregister_netdev(ndev);
sysfs_remove_group(&lp->dev->kobj, &temac_attr_group);
of_node_put(lp->phy_node);
- lp->phy_node = NULL;
- iounmap(lp->regs);
- if (lp->sdma_regs)
- iounmap(lp->sdma_regs);
- free_netdev(ndev);
+ temac_mdio_teardown(lp);
return 0;
}

diff --git a/drivers/net/ethernet/xilinx/ll_temac_mdio.c b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
index f5e83ac..a0b365e 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_mdio.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
@@ -57,8 +57,9 @@ static int temac_mdio_write(struct mii_bus *bus, int phy_id, int reg, u16 val)
return 0;
}

-int temac_mdio_setup(struct temac_local *lp, struct device_node *np)
+int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)
{
+ struct device_node *np = dev_of_node(&pdev->dev);
struct mii_bus *bus;
u32 bus_hz;
int clk_div;
@@ -81,7 +82,7 @@ int temac_mdio_setup(struct temac_local *lp, struct device_node *np)
temac_indirect_out32(lp, XTE_MC_OFFSET, 1 << 6 | clk_div);
mutex_unlock(&lp->indirect_mutex);

- bus = mdiobus_alloc();
+ bus = devm_mdiobus_alloc(&pdev->dev);
if (!bus)
return -ENOMEM;

@@ -98,23 +99,16 @@ int temac_mdio_setup(struct temac_local *lp, struct device_node *np)

rc = of_mdiobus_register(bus, np);
if (rc)
- goto err_register;
+ return rc;

mutex_lock(&lp->indirect_mutex);
dev_dbg(lp->dev, "MDIO bus registered; MC:%x\n",
temac_indirect_in32(lp, XTE_MC_OFFSET));
mutex_unlock(&lp->indirect_mutex);
return 0;
-
- err_register:
- mdiobus_free(bus);
- return rc;
}

void temac_mdio_teardown(struct temac_local *lp)
{
mdiobus_unregister(lp->mii_bus);
- mdiobus_free(lp->mii_bus);
- lp->mii_bus = NULL;
}
-
--
2.4.11

2019-04-29 08:36:19

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 03/12] net: ll_temac: Fix support for 64-bit platforms

The use of buffer descriptor APP4 field (32-bit) for storing skb pointer
obviously does not work on 64-bit platforms.
As APP3 is also unused, we can use that to store the other half of 64-bit
pointer values.

Contrary to what is hinted at in commit message of commit 15bfe05c8d63
("net: ethernet: xilinx: Mark XILINX_LL_TEMAC broken on 64-bit")
there are no other pointers stored in cdmac_bd.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/Kconfig | 1 -
drivers/net/ethernet/xilinx/ll_temac_main.c | 35 ++++++++++++++++++++++++++---
2 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig
index da4ec57..6d68c8a 100644
--- a/drivers/net/ethernet/xilinx/Kconfig
+++ b/drivers/net/ethernet/xilinx/Kconfig
@@ -34,7 +34,6 @@ config XILINX_AXI_EMAC
config XILINX_LL_TEMAC
tristate "Xilinx LL TEMAC (LocalLink Tri-mode Ethernet MAC) driver"
depends on (PPC || MICROBLAZE)
- depends on !64BIT || BROKEN
select PHYLIB
---help---
This driver supports the Xilinx 10/100/1000 LocalLink TEMAC
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index fddd1b3..bcafb89 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -619,11 +619,39 @@ static void temac_adjust_link(struct net_device *ndev)
mutex_unlock(&lp->indirect_mutex);
}

+#ifdef CONFIG_64BIT
+
+void ptr_to_txbd(void *p, struct cdmac_bd *bd)
+{
+ bd->app3 = (u32)(((u64)p) >> 32);
+ bd->app4 = (u32)((u64)p & 0xFFFFFFFF);
+}
+
+void *ptr_from_txbd(struct cdmac_bd *bd)
+{
+ return (void *)(((u64)(bd->app3) << 32) | bd->app4);
+}
+
+#else
+
+void ptr_to_txbd(void *p, struct cmdac_bd *bd)
+{
+ bd->app4 = (u32)p;
+}
+
+void *ptr_from_txbd(struct cdmac_bd *bd)
+{
+ return (void *)(bd->app4);
+}
+
+#endif
+
static void temac_start_xmit_done(struct net_device *ndev)
{
struct temac_local *lp = netdev_priv(ndev);
struct cdmac_bd *cur_p;
unsigned int stat = 0;
+ struct sk_buff *skb;

cur_p = &lp->tx_bd_v[lp->tx_bd_ci];
stat = cur_p->app0;
@@ -631,8 +659,9 @@ static void temac_start_xmit_done(struct net_device *ndev)
while (stat & STS_CTRL_APP0_CMPLT) {
dma_unmap_single(ndev->dev.parent, cur_p->phys, cur_p->len,
DMA_TO_DEVICE);
- if (cur_p->app4)
- dev_consume_skb_irq((struct sk_buff *)cur_p->app4);
+ skb = (struct sk_buff *)ptr_from_txbd(cur_p);
+ if (skb)
+ dev_consume_skb_irq(skb);
cur_p->app0 = 0;
cur_p->app1 = 0;
cur_p->app2 = 0;
@@ -711,7 +740,7 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
cur_p->len = skb_headlen(skb);
cur_p->phys = dma_map_single(ndev->dev.parent, skb->data,
skb_headlen(skb), DMA_TO_DEVICE);
- cur_p->app4 = (unsigned long)skb;
+ ptr_to_txbd((void *)skb, cur_p);

for (ii = 0; ii < num_frag; ii++) {
lp->tx_bd_tail++;
--
2.4.11

2019-04-29 08:36:22

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 06/12] net: ll_temac: Allow use on x86 platforms

With little-endian and 64-bit support in place, the ll_temac driver can
now be used on x86 and x86_64 platforms.

And while at it, enable COMPILE_TEST also.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/Kconfig | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig
index 6d68c8a..db448fa 100644
--- a/drivers/net/ethernet/xilinx/Kconfig
+++ b/drivers/net/ethernet/xilinx/Kconfig
@@ -5,7 +5,7 @@
config NET_VENDOR_XILINX
bool "Xilinx devices"
default y
- depends on PPC || PPC32 || MICROBLAZE || ARCH_ZYNQ || MIPS
+ depends on PPC || PPC32 || MICROBLAZE || ARCH_ZYNQ || MIPS || X86 || COMPILE_TEST
---help---
If you have a network (Ethernet) card belonging to this class, say Y.

@@ -33,7 +33,7 @@ config XILINX_AXI_EMAC

config XILINX_LL_TEMAC
tristate "Xilinx LL TEMAC (LocalLink Tri-mode Ethernet MAC) driver"
- depends on (PPC || MICROBLAZE)
+ depends on PPC || MICROBLAZE || X86 || COMPILE_TEST
select PHYLIB
---help---
This driver supports the Xilinx 10/100/1000 LocalLink TEMAC
--
2.4.11

2019-04-29 08:36:35

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 10/12] net: ll_temac: Replace bad usage of msleep() with usleep_range()

Use usleep_range() to avoid problems with msleep() actually sleeping
much longer than expected.

Signed-off-by: Esben Haabendal <[email protected]>
Reviewed-by: Andrew Lunn <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac_main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 2d50646..5e7b8f0 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -92,7 +92,7 @@ int temac_indirect_busywait(struct temac_local *lp)
WARN_ON(1);
return -ETIMEDOUT;
}
- msleep(1);
+ usleep_range(500, 1000);
}
return 0;
}
--
2.4.11

2019-04-29 08:36:41

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 12/12] net: ll_temac: Enable DMA when ready, not before

As soon as TAILDESCR_PTR is written, DMA transfers might start.
Let's ensure we are ready to receive DMA IRQ's before doing that.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac_main.c | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 5833f02..a230f6a 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -316,17 +316,21 @@ static int temac_dma_bd_init(struct net_device *ndev)
CHNL_CTRL_IRQ_EN | CHNL_CTRL_IRQ_ERR_EN |
CHNL_CTRL_IRQ_DLY_EN | CHNL_CTRL_IRQ_COAL_EN);

- lp->dma_out(lp, RX_CURDESC_PTR, lp->rx_bd_p);
- lp->dma_out(lp, RX_TAILDESC_PTR,
- lp->rx_bd_p + (sizeof(*lp->rx_bd_v) * (RX_BD_NUM - 1)));
- lp->dma_out(lp, TX_CURDESC_PTR, lp->tx_bd_p);
-
/* Init descriptor indexes */
lp->tx_bd_ci = 0;
lp->tx_bd_next = 0;
lp->tx_bd_tail = 0;
lp->rx_bd_ci = 0;

+ /* Enable RX DMA transfers */
+ wmb();
+ lp->dma_out(lp, RX_CURDESC_PTR, lp->rx_bd_p);
+ lp->dma_out(lp, RX_TAILDESC_PTR,
+ lp->rx_bd_p + (sizeof(*lp->rx_bd_v) * (RX_BD_NUM - 1)));
+
+ /* Prepare for TX DMA transfer */
+ lp->dma_out(lp, TX_CURDESC_PTR, lp->tx_bd_p);
+
return 0;

out:
@@ -790,6 +794,7 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
skb_tx_timestamp(skb);

/* Kick off the transfer */
+ wmb();
lp->dma_out(lp, TX_TAILDESC_PTR, tail_p); /* DMA start */

return NETDEV_TX_OK;
--
2.4.11

2019-04-29 08:36:48

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 11/12] net: ll_temac: Allow configuration of IRQ coalescing

This allows custom setup of IRQ coalescing for platforms using legacy
platform_device. The irq timeout and count parameters can be used for
tuning cpu load vs. latency.

I have maintained the 0x00000400 bit in TX_CHNL_CTRL. It is specified as
unused in the documentation I have available. It does not make any
difference in the hardware I have available, so it is left in to not risk
breaking other platforms where it might be used.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac.h | 4 +++
drivers/net/ethernet/xilinx/ll_temac_main.c | 40 +++++++++++++++++++--------
include/linux/platform_data/xilinx-ll-temac.h | 5 ++++
3 files changed, 37 insertions(+), 12 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h
index 990f9ed..1aeda08 100644
--- a/drivers/net/ethernet/xilinx/ll_temac.h
+++ b/drivers/net/ethernet/xilinx/ll_temac.h
@@ -375,6 +375,10 @@ struct temac_local {
int tx_bd_next;
int tx_bd_tail;
int rx_bd_ci;
+
+ /* DMA channel control setup */
+ u32 tx_chnl_ctrl;
+ u32 rx_chnl_ctrl;
};

/* Wrappers for temac_ior()/temac_iow() function pointers above */
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 5e7b8f0..5833f02 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -306,18 +306,15 @@ static int temac_dma_bd_init(struct net_device *ndev)
lp->rx_bd_v[i].app0 = cpu_to_be32(STS_CTRL_APP0_IRQONEND);
}

- lp->dma_out(lp, TX_CHNL_CTRL, 0x10220400 |
- CHNL_CTRL_IRQ_EN |
- CHNL_CTRL_IRQ_DLY_EN |
- CHNL_CTRL_IRQ_COAL_EN);
- /* 0x10220483 */
- /* 0x00100483 */
- lp->dma_out(lp, RX_CHNL_CTRL, 0xff070000 |
- CHNL_CTRL_IRQ_EN |
- CHNL_CTRL_IRQ_DLY_EN |
- CHNL_CTRL_IRQ_COAL_EN |
- CHNL_CTRL_IRQ_IOE);
- /* 0xff010283 */
+ /* Configure DMA channel (irq setup) */
+ lp->dma_out(lp, TX_CHNL_CTRL, lp->tx_chnl_ctrl |
+ 0x00000400 | // Use 1 Bit Wide Counters. Currently Not Used!
+ CHNL_CTRL_IRQ_EN | CHNL_CTRL_IRQ_ERR_EN |
+ CHNL_CTRL_IRQ_DLY_EN | CHNL_CTRL_IRQ_COAL_EN);
+ lp->dma_out(lp, RX_CHNL_CTRL, lp->rx_chnl_ctrl |
+ CHNL_CTRL_IRQ_IOE |
+ CHNL_CTRL_IRQ_EN | CHNL_CTRL_IRQ_ERR_EN |
+ CHNL_CTRL_IRQ_DLY_EN | CHNL_CTRL_IRQ_COAL_EN);

lp->dma_out(lp, RX_CURDESC_PTR, lp->rx_bd_p);
lp->dma_out(lp, RX_TAILDESC_PTR,
@@ -1187,6 +1184,13 @@ static int temac_probe(struct platform_device *pdev)
lp->rx_irq = irq_of_parse_and_map(dma_np, 0);
lp->tx_irq = irq_of_parse_and_map(dma_np, 1);

+ /* Use defaults for IRQ delay/coalescing setup. These
+ * are configuration values, so does not belong in
+ * device-tree.
+ */
+ lp->tx_chnl_ctrl = 0x10220000;
+ lp->rx_chnl_ctrl = 0xff070000;
+
/* Finished with the DMA node; drop the reference */
of_node_put(dma_np);
} else if (pdata) {
@@ -1210,6 +1214,18 @@ static int temac_probe(struct platform_device *pdev)
/* Get DMA RX and TX interrupts */
lp->rx_irq = platform_get_irq(pdev, 0);
lp->tx_irq = platform_get_irq(pdev, 1);
+
+ /* IRQ delay/coalescing setup */
+ if (pdata->tx_irq_timeout || pdata->tx_irq_count)
+ lp->tx_chnl_ctrl = (pdata->tx_irq_timeout << 24) |
+ (pdata->tx_irq_count << 16);
+ else
+ lp->tx_chnl_ctrl = 0x10220000;
+ if (pdata->rx_irq_timeout || pdata->rx_irq_count)
+ lp->rx_chnl_ctrl = (pdata->rx_irq_timeout << 24) |
+ (pdata->rx_irq_count << 16);
+ else
+ lp->rx_chnl_ctrl = 0xff070000;
}

/* Error handle returned DMA RX and TX interrupts */
diff --git a/include/linux/platform_data/xilinx-ll-temac.h b/include/linux/platform_data/xilinx-ll-temac.h
index b0b8238..368530f 100644
--- a/include/linux/platform_data/xilinx-ll-temac.h
+++ b/include/linux/platform_data/xilinx-ll-temac.h
@@ -22,6 +22,11 @@ struct ll_temac_platform_data {
* they share the same DCR bus bridge.
*/
struct mutex *indirect_mutex;
+ /* DMA channel control setup */
+ u8 tx_irq_timeout; /* TX Interrupt Delay Time-out */
+ u8 tx_irq_count; /* TX Interrupt Coalescing Threshold Count */
+ u8 rx_irq_timeout; /* RX Interrupt Delay Time-out */
+ u8 rx_irq_count; /* RX Interrupt Coalescing Threshold Count */
};

#endif /* __LINUX_XILINX_LL_TEMAC_H */
--
2.4.11

2019-04-29 08:37:35

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 05/12] net: ll_temac: Fix support for little-endian platforms

Both TEMAC and SDMA is big-endian, so make sure that all values in SDMA
buffer descriptors (cmdac_bd) are handled as big-endian, independent of the
host endianness. With all currently supported platforms being big-endian,
this change does not make a change for any of them.

Note, when using app3 and app4 for piggybacking skb pointers there is no
need to care about endianness, as neither TEMAC nor SDMA access app3 and
app4 in TX buffer descriptors.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac_main.c | 90 ++++++++++++++++-------------
1 file changed, 51 insertions(+), 39 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 58c6713..25d9a35 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -258,6 +258,7 @@ static int temac_dma_bd_init(struct net_device *ndev)
{
struct temac_local *lp = netdev_priv(ndev);
struct sk_buff *skb;
+ dma_addr_t skb_dma_addr;
int i;

lp->rx_skb = devm_kcalloc(&ndev->dev, RX_BD_NUM, sizeof(*lp->rx_skb),
@@ -280,13 +281,15 @@ static int temac_dma_bd_init(struct net_device *ndev)
goto out;

for (i = 0; i < TX_BD_NUM; i++) {
- lp->tx_bd_v[i].next = lp->tx_bd_p +
- sizeof(*lp->tx_bd_v) * ((i + 1) % TX_BD_NUM);
+ lp->tx_bd_v[i].next = cpu_to_be32(
+ lp->tx_bd_p
+ + sizeof(*lp->tx_bd_v) * ((i + 1) % TX_BD_NUM));
}

for (i = 0; i < RX_BD_NUM; i++) {
- lp->rx_bd_v[i].next = lp->rx_bd_p +
- sizeof(*lp->rx_bd_v) * ((i + 1) % RX_BD_NUM);
+ lp->rx_bd_v[i].next = cpu_to_be32(
+ lp->rx_bd_p
+ + sizeof(*lp->rx_bd_v) * ((i + 1) % RX_BD_NUM));

skb = netdev_alloc_skb_ip_align(ndev,
XTE_MAX_JUMBO_FRAME_SIZE);
@@ -295,12 +298,12 @@ static int temac_dma_bd_init(struct net_device *ndev)

lp->rx_skb[i] = skb;
/* returns physical address of skb->data */
- lp->rx_bd_v[i].phys = dma_map_single(ndev->dev.parent,
- skb->data,
- XTE_MAX_JUMBO_FRAME_SIZE,
- DMA_FROM_DEVICE);
- lp->rx_bd_v[i].len = XTE_MAX_JUMBO_FRAME_SIZE;
- lp->rx_bd_v[i].app0 = STS_CTRL_APP0_IRQONEND;
+ skb_dma_addr = dma_map_single(ndev->dev.parent, skb->data,
+ XTE_MAX_JUMBO_FRAME_SIZE,
+ DMA_FROM_DEVICE);
+ lp->rx_bd_v[i].phys = cpu_to_be32(skb_dma_addr);
+ lp->rx_bd_v[i].len = cpu_to_be32(XTE_MAX_JUMBO_FRAME_SIZE);
+ lp->rx_bd_v[i].app0 = cpu_to_be32(STS_CTRL_APP0_IRQONEND);
}

lp->dma_out(lp, TX_CHNL_CTRL, 0x10220400 |
@@ -676,11 +679,11 @@ static void temac_start_xmit_done(struct net_device *ndev)
struct sk_buff *skb;

cur_p = &lp->tx_bd_v[lp->tx_bd_ci];
- stat = cur_p->app0;
+ stat = be32_to_cpu(cur_p->app0);

while (stat & STS_CTRL_APP0_CMPLT) {
- dma_unmap_single(ndev->dev.parent, cur_p->phys, cur_p->len,
- DMA_TO_DEVICE);
+ dma_unmap_single(ndev->dev.parent, be32_to_cpu(cur_p->phys),
+ be32_to_cpu(cur_p->len), DMA_TO_DEVICE);
skb = (struct sk_buff *)ptr_from_txbd(cur_p);
if (skb)
dev_consume_skb_irq(skb);
@@ -691,14 +694,14 @@ static void temac_start_xmit_done(struct net_device *ndev)
cur_p->app4 = 0;

ndev->stats.tx_packets++;
- ndev->stats.tx_bytes += cur_p->len;
+ ndev->stats.tx_bytes += be32_to_cpu(cur_p->len);

lp->tx_bd_ci++;
if (lp->tx_bd_ci >= TX_BD_NUM)
lp->tx_bd_ci = 0;

cur_p = &lp->tx_bd_v[lp->tx_bd_ci];
- stat = cur_p->app0;
+ stat = be32_to_cpu(cur_p->app0);
}

netif_wake_queue(ndev);
@@ -732,7 +735,7 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
{
struct temac_local *lp = netdev_priv(ndev);
struct cdmac_bd *cur_p;
- dma_addr_t start_p, tail_p;
+ dma_addr_t start_p, tail_p, skb_dma_addr;
int ii;
unsigned long num_frag;
skb_frag_t *frag;
@@ -753,15 +756,17 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
unsigned int csum_start_off = skb_checksum_start_offset(skb);
unsigned int csum_index_off = csum_start_off + skb->csum_offset;

- cur_p->app0 |= 1; /* TX Checksum Enabled */
- cur_p->app1 = (csum_start_off << 16) | csum_index_off;
+ cur_p->app0 |= cpu_to_be32(0x000001); /* TX Checksum Enabled */
+ cur_p->app1 = cpu_to_be32((csum_start_off << 16)
+ | csum_index_off);
cur_p->app2 = 0; /* initial checksum seed */
}

- cur_p->app0 |= STS_CTRL_APP0_SOP;
- cur_p->len = skb_headlen(skb);
- cur_p->phys = dma_map_single(ndev->dev.parent, skb->data,
- skb_headlen(skb), DMA_TO_DEVICE);
+ cur_p->app0 |= cpu_to_be32(STS_CTRL_APP0_SOP);
+ skb_dma_addr = dma_map_single(ndev->dev.parent, skb->data,
+ skb_headlen(skb), DMA_TO_DEVICE);
+ cur_p->len = cpu_to_be32(skb_headlen(skb));
+ cur_p->phys = cpu_to_be32(skb_dma_addr);
ptr_to_txbd((void *)skb, cur_p);

for (ii = 0; ii < num_frag; ii++) {
@@ -770,14 +775,15 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
lp->tx_bd_tail = 0;

cur_p = &lp->tx_bd_v[lp->tx_bd_tail];
- cur_p->phys = dma_map_single(ndev->dev.parent,
- skb_frag_address(frag),
- skb_frag_size(frag), DMA_TO_DEVICE);
- cur_p->len = skb_frag_size(frag);
+ skb_dma_addr = dma_map_single(
+ ndev->dev.parent, skb_frag_address(frag),
+ skb_frag_size(frag), DMA_TO_DEVICE);
+ cur_p->phys = cpu_to_be32(skb_dma_addr);
+ cur_p->len = cpu_to_be32(skb_frag_size(frag));
cur_p->app0 = 0;
frag++;
}
- cur_p->app0 |= STS_CTRL_APP0_EOP;
+ cur_p->app0 |= cpu_to_be32(STS_CTRL_APP0_EOP);

tail_p = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * lp->tx_bd_tail;
lp->tx_bd_tail++;
@@ -799,7 +805,7 @@ static void ll_temac_recv(struct net_device *ndev)
struct sk_buff *skb, *new_skb;
unsigned int bdstat;
struct cdmac_bd *cur_p;
- dma_addr_t tail_p;
+ dma_addr_t tail_p, skb_dma_addr;
int length;
unsigned long flags;

@@ -808,14 +814,14 @@ static void ll_temac_recv(struct net_device *ndev)
tail_p = lp->rx_bd_p + sizeof(*lp->rx_bd_v) * lp->rx_bd_ci;
cur_p = &lp->rx_bd_v[lp->rx_bd_ci];

- bdstat = cur_p->app0;
+ bdstat = be32_to_cpu(cur_p->app0);
while ((bdstat & STS_CTRL_APP0_CMPLT)) {

skb = lp->rx_skb[lp->rx_bd_ci];
- length = cur_p->app4 & 0x3FFF;
+ length = be32_to_cpu(cur_p->app4) & 0x3FFF;

- dma_unmap_single(ndev->dev.parent, cur_p->phys, length,
- DMA_FROM_DEVICE);
+ dma_unmap_single(ndev->dev.parent, be32_to_cpu(cur_p->phys),
+ length, DMA_FROM_DEVICE);

skb_put(skb, length);
skb->protocol = eth_type_trans(skb, ndev);
@@ -826,7 +832,12 @@ static void ll_temac_recv(struct net_device *ndev)
(skb->protocol == htons(ETH_P_IP)) &&
(skb->len > 64)) {

- skb->csum = cur_p->app3 & 0xFFFF;
+ /* Convert from device endianness (be32) to cpu
+ * endiannes, and if necessary swap the bytes
+ * (back) for proper IP checksum byte order
+ * (be16).
+ */
+ skb->csum = htons(be32_to_cpu(cur_p->app3) & 0xFFFF);
skb->ip_summed = CHECKSUM_COMPLETE;
}

@@ -843,11 +854,12 @@ static void ll_temac_recv(struct net_device *ndev)
return;
}

- cur_p->app0 = STS_CTRL_APP0_IRQONEND;
- cur_p->phys = dma_map_single(ndev->dev.parent, new_skb->data,
- XTE_MAX_JUMBO_FRAME_SIZE,
- DMA_FROM_DEVICE);
- cur_p->len = XTE_MAX_JUMBO_FRAME_SIZE;
+ cur_p->app0 = cpu_to_be32(STS_CTRL_APP0_IRQONEND);
+ skb_dma_addr = dma_map_single(ndev->dev.parent, new_skb->data,
+ XTE_MAX_JUMBO_FRAME_SIZE,
+ DMA_FROM_DEVICE);
+ cur_p->phys = cpu_to_be32(skb_dma_addr);
+ cur_p->len = cpu_to_be32(XTE_MAX_JUMBO_FRAME_SIZE);
lp->rx_skb[lp->rx_bd_ci] = new_skb;

lp->rx_bd_ci++;
@@ -855,7 +867,7 @@ static void ll_temac_recv(struct net_device *ndev)
lp->rx_bd_ci = 0;

cur_p = &lp->rx_bd_v[lp->rx_bd_ci];
- bdstat = cur_p->app0;
+ bdstat = be32_to_cpu(cur_p->app0);
}
lp->dma_out(lp, RX_TAILDESC_PTR, tail_p);

--
2.4.11

2019-04-29 08:37:37

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 09/12] net: ll_temac: Fix bug causing buffer descriptor overrun

As we are actually using a BD for both the skb and each frag contained in
it, the oldest TX BD would be overwritten when there was exactly one BD
less than needed.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac_main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 72ec338..2d50646 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -745,7 +745,7 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
start_p = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * lp->tx_bd_tail;
cur_p = &lp->tx_bd_v[lp->tx_bd_tail];

- if (temac_check_tx_bd_space(lp, num_frag)) {
+ if (temac_check_tx_bd_space(lp, num_frag + 1)) {
if (!netif_queue_stopped(ndev))
netif_stop_queue(ndev);
return NETDEV_TX_BUSY;
--
2.4.11

2019-04-29 08:37:51

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 08/12] net: ll_temac: Fix iommu/swiotlb leak

Unmap the actual buffer length, not the amount of data received, avoiding
resource exhaustion of swiotlb (seen on x86_64 platform).

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac_main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 1c5d126..72ec338 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -821,7 +821,7 @@ static void ll_temac_recv(struct net_device *ndev)
length = be32_to_cpu(cur_p->app4) & 0x3FFF;

dma_unmap_single(ndev->dev.parent, be32_to_cpu(cur_p->phys),
- length, DMA_FROM_DEVICE);
+ XTE_MAX_JUMBO_FRAME_SIZE, DMA_FROM_DEVICE);

skb_put(skb, length);
skb->protocol = eth_type_trans(skb, ndev);
--
2.4.11

2019-04-29 08:38:03

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 07/12] net: ll_temac: Support indirect_mutex share within TEMAC IP

Indirect register access goes through a DCR bus bridge, which
allows only one outstanding transaction. And to make matters
worse, each TEMAC IP block contains two Ethernet interfaces, and
although they seem to have separate registers for indirect access,
they actually share the registers. Or to be more specific, MSW, LSW
and CTL registers are physically shared between Ethernet interfaces
in same TEMAC IP, with RDY register being (almost) specificic to
the Ethernet interface. The 0x10000 bit in RDY reflects combined
bus ready state though.

So we need to take care to synchronize not only within a single
device, but also between devices in same TEMAC IP.

This commit allows to do that with legacy platform devices.

For OF devices, the xlnx,compound parent of the temac node should be
used to find siblings, and setup a shared indirect_mutex between them.
I will leave this work to somebody else, as I don't have hardware to
test that. No regression is introduced by that, as before this commit
using two Ethernet interfaces in same TEMAC block is simply broken.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac.h | 5 ++++-
drivers/net/ethernet/xilinx/ll_temac_main.c | 31 +++++++++++++++++----------
drivers/net/ethernet/xilinx/ll_temac_mdio.c | 16 +++++++-------
include/linux/platform_data/xilinx-ll-temac.h | 6 ++++++
4 files changed, 38 insertions(+), 20 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h
index 23d8dd5..990f9ed 100644
--- a/drivers/net/ethernet/xilinx/ll_temac.h
+++ b/drivers/net/ethernet/xilinx/ll_temac.h
@@ -358,7 +358,10 @@ struct temac_local {

struct sk_buff **rx_skb;
spinlock_t rx_lock;
- struct mutex indirect_mutex;
+ /* For synchronization of indirect register access. Must be
+ * shared mutex between interfaces in same TEMAC block.
+ */
+ struct mutex *indirect_mutex;
u32 options; /* Current options word */
int last_link;
unsigned int temac_features;
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 25d9a35..1c5d126 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -346,7 +346,7 @@ static void temac_do_set_mac_address(struct net_device *ndev)
struct temac_local *lp = netdev_priv(ndev);

/* set up unicast MAC address filter set its mac address */
- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
temac_indirect_out32(lp, XTE_UAW0_OFFSET,
(ndev->dev_addr[0]) |
(ndev->dev_addr[1] << 8) |
@@ -357,7 +357,7 @@ static void temac_do_set_mac_address(struct net_device *ndev)
temac_indirect_out32(lp, XTE_UAW1_OFFSET,
(ndev->dev_addr[4] & 0x000000ff) |
(ndev->dev_addr[5] << 8));
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);
}

static int temac_init_mac_address(struct net_device *ndev, const void *address)
@@ -386,7 +386,7 @@ static void temac_set_multicast_list(struct net_device *ndev)
u32 multi_addr_msw, multi_addr_lsw, val;
int i;

- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
if (ndev->flags & (IFF_ALLMULTI | IFF_PROMISC) ||
netdev_mc_count(ndev) > MULTICAST_CAM_TABLE_NUM) {
/*
@@ -425,7 +425,7 @@ static void temac_set_multicast_list(struct net_device *ndev)
temac_indirect_out32(lp, XTE_MAW1_OFFSET, 0);
dev_info(&ndev->dev, "Promiscuous mode disabled.\n");
}
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);
}

static struct temac_option {
@@ -517,7 +517,7 @@ static u32 temac_setoptions(struct net_device *ndev, u32 options)
struct temac_option *tp = &temac_options[0];
int reg;

- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
while (tp->opt) {
reg = temac_indirect_in32(lp, tp->reg) & ~tp->m_or;
if (options & tp->opt)
@@ -526,7 +526,7 @@ static u32 temac_setoptions(struct net_device *ndev, u32 options)
tp++;
}
lp->options |= options;
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);

return 0;
}
@@ -545,7 +545,7 @@ static void temac_device_reset(struct net_device *ndev)

dev_dbg(&ndev->dev, "%s()\n", __func__);

- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
/* Reset the receiver and wait for it to finish reset */
temac_indirect_out32(lp, XTE_RXC1_OFFSET, XTE_RXC1_RXRST_MASK);
timeout = 1000;
@@ -597,7 +597,7 @@ static void temac_device_reset(struct net_device *ndev)
temac_indirect_out32(lp, XTE_TXC_OFFSET, 0);
temac_indirect_out32(lp, XTE_FCC_OFFSET, XTE_FCC_RXFLO_MASK);

- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);

/* Sync default options with HW
* but leave receiver and transmitter disabled. */
@@ -625,7 +625,7 @@ static void temac_adjust_link(struct net_device *ndev)
/* hash together the state values to decide if something has changed */
link_state = phy->speed | (phy->duplex << 1) | phy->link;

- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
if (lp->last_link != link_state) {
mii_speed = temac_indirect_in32(lp, XTE_EMCFG_OFFSET);
mii_speed &= ~XTE_EMCFG_LINKSPD_MASK;
@@ -641,7 +641,7 @@ static void temac_adjust_link(struct net_device *ndev)
lp->last_link = link_state;
phy_print_status(phy);
}
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);
}

#ifdef CONFIG_64BIT
@@ -1092,7 +1092,16 @@ static int temac_probe(struct platform_device *pdev)
lp->dev = &pdev->dev;
lp->options = XTE_OPTION_DEFAULTS;
spin_lock_init(&lp->rx_lock);
- mutex_init(&lp->indirect_mutex);
+
+ /* Setup mutex for synchronization of indirect register access */
+ if (pdata) {
+ if (!pdata->indirect_mutex) {
+ dev_err(&pdev->dev,
+ "indirect_mutex missing in platform_data\n");
+ return -EINVAL;
+ }
+ lp->indirect_mutex = pdata->indirect_mutex;
+ }

/* map device registers */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
diff --git a/drivers/net/ethernet/xilinx/ll_temac_mdio.c b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
index c5307e5..c2a1170 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_mdio.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
@@ -29,10 +29,10 @@ static int temac_mdio_read(struct mii_bus *bus, int phy_id, int reg)
/* Write the PHY address to the MIIM Access Initiator register.
* When the transfer completes, the PHY register value will appear
* in the LSW0 register */
- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
temac_iow(lp, XTE_LSW0_OFFSET, (phy_id << 5) | reg);
rc = temac_indirect_in32(lp, XTE_MIIMAI_OFFSET);
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);

dev_dbg(lp->dev, "temac_mdio_read(phy_id=%i, reg=%x) == %x\n",
phy_id, reg, rc);
@@ -50,10 +50,10 @@ static int temac_mdio_write(struct mii_bus *bus, int phy_id, int reg, u16 val)
/* First write the desired value into the write data register
* and then write the address into the access initiator register
*/
- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
temac_indirect_out32(lp, XTE_MGTDR_OFFSET, val);
temac_indirect_out32(lp, XTE_MIIMAI_OFFSET, (phy_id << 5) | reg);
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);

return 0;
}
@@ -87,9 +87,9 @@ int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)

/* Enable the MDIO bus by asserting the enable bit and writing
* in the clock config */
- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
temac_indirect_out32(lp, XTE_MC_OFFSET, 1 << 6 | clk_div);
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);

bus = devm_mdiobus_alloc(&pdev->dev);
if (!bus)
@@ -116,10 +116,10 @@ int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)
if (rc)
return rc;

- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
dev_dbg(lp->dev, "MDIO bus registered; MC:%x\n",
temac_indirect_in32(lp, XTE_MC_OFFSET));
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);
return 0;
}

diff --git a/include/linux/platform_data/xilinx-ll-temac.h b/include/linux/platform_data/xilinx-ll-temac.h
index af87927..b0b8238 100644
--- a/include/linux/platform_data/xilinx-ll-temac.h
+++ b/include/linux/platform_data/xilinx-ll-temac.h
@@ -16,6 +16,12 @@ struct ll_temac_platform_data {
phy_interface_t phy_interface; /* PHY interface mode */
bool reg_little_endian; /* Little endian TEMAC register access */
bool dma_little_endian; /* Little endian DMA register access */
+ /* Pre-initialized mutex to use for synchronizing indirect
+ * register access. When using both interfaces of a single
+ * TEMAC IP block, the same mutex should be passed here, as
+ * they share the same DCR bus bridge.
+ */
+ struct mutex *indirect_mutex;
};

#endif /* __LINUX_XILINX_LL_TEMAC_H */
--
2.4.11

2019-04-29 08:38:08

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 04/12] net: ll_temac: Add support for non-native register endianness

Replace the powerpc specific MMIO register access functions with the
generic big-endian mmio access functions, and add support for
little-endian access depending on configuration.

Big-endian access is maintained as the default, but little-endian can
be configured in device-tree binding or in platform data.

The temac_ior()/temac_iow() functions are replaced with macro wrappers
to avoid modifying existing code more than necessary.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac.h | 12 ++--
drivers/net/ethernet/xilinx/ll_temac_main.c | 87 +++++++++++++++++++++------
include/linux/platform_data/xilinx-ll-temac.h | 2 +
3 files changed, 79 insertions(+), 22 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h
index e338b4f..23d8dd5 100644
--- a/drivers/net/ethernet/xilinx/ll_temac.h
+++ b/drivers/net/ethernet/xilinx/ll_temac.h
@@ -347,8 +347,10 @@ struct temac_local {
#ifdef CONFIG_PPC_DCR
dcr_host_t sdma_dcrs;
#endif
- u32 (*dma_in)(struct temac_local *, int);
- void (*dma_out)(struct temac_local *, int, u32);
+ u32 (*temac_ior)(struct temac_local *lp, int offset);
+ void (*temac_iow)(struct temac_local *lp, int offset, u32 value);
+ u32 (*dma_in)(struct temac_local *lp, int reg);
+ void (*dma_out)(struct temac_local *lp, int reg, u32 value);

int tx_irq;
int rx_irq;
@@ -372,9 +374,11 @@ struct temac_local {
int rx_bd_ci;
};

+/* Wrappers for temac_ior()/temac_iow() function pointers above */
+#define temac_ior(lp, o) ((lp)->temac_ior(lp, o))
+#define temac_iow(lp, o, v) ((lp)->temac_iow(lp, o, v))
+
/* xilinx_temac.c */
-u32 temac_ior(struct temac_local *lp, int offset);
-void temac_iow(struct temac_local *lp, int offset, u32 value);
int temac_indirect_busywait(struct temac_local *lp);
u32 temac_indirect_in32(struct temac_local *lp, int reg);
void temac_indirect_out32(struct temac_local *lp, int reg, u32 value);
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index bcafb89..58c6713 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -63,14 +63,24 @@
* Low level register access functions
*/

-u32 temac_ior(struct temac_local *lp, int offset)
+u32 _temac_ior_be(struct temac_local *lp, int offset)
{
- return in_be32(lp->regs + offset);
+ return ioread32be(lp->regs + offset);
}

-void temac_iow(struct temac_local *lp, int offset, u32 value)
+void _temac_iow_be(struct temac_local *lp, int offset, u32 value)
{
- out_be32(lp->regs + offset, value);
+ return iowrite32be(value, lp->regs + offset);
+}
+
+u32 _temac_ior_le(struct temac_local *lp, int offset)
+{
+ return ioread32(lp->regs + offset);
+}
+
+void _temac_iow_le(struct temac_local *lp, int offset, u32 value)
+{
+ return iowrite32(value, lp->regs + offset);
}

int temac_indirect_busywait(struct temac_local *lp)
@@ -121,23 +131,35 @@ void temac_indirect_out32(struct temac_local *lp, int reg, u32 value)
}

/**
- * temac_dma_in32 - Memory mapped DMA read, this function expects a
- * register input that is based on DCR word addresses which
- * are then converted to memory mapped byte addresses
+ * temac_dma_in32_* - Memory mapped DMA read, these function expects a
+ * register input that is based on DCR word addresses which are then
+ * converted to memory mapped byte addresses. To be assigned to
+ * lp->dma_in32.
*/
-static u32 temac_dma_in32(struct temac_local *lp, int reg)
+static u32 temac_dma_in32_be(struct temac_local *lp, int reg)
{
- return in_be32(lp->sdma_regs + (reg << 2));
+ return ioread32be(lp->sdma_regs + (reg << 2));
+}
+
+static u32 temac_dma_in32_le(struct temac_local *lp, int reg)
+{
+ return ioread32(lp->sdma_regs + (reg << 2));
}

/**
- * temac_dma_out32 - Memory mapped DMA read, this function expects a
- * register input that is based on DCR word addresses which
- * are then converted to memory mapped byte addresses
+ * temac_dma_out32_* - Memory mapped DMA read, these function expects
+ * a register input that is based on DCR word addresses which are then
+ * converted to memory mapped byte addresses. To be assigned to
+ * lp->dma_out32.
*/
-static void temac_dma_out32(struct temac_local *lp, int reg, u32 value)
+static void temac_dma_out32_be(struct temac_local *lp, int reg, u32 value)
+{
+ iowrite32be(value, lp->sdma_regs + (reg << 2));
+}
+
+static void temac_dma_out32_le(struct temac_local *lp, int reg, u32 value)
{
- out_be32(lp->sdma_regs + (reg << 2), value);
+ iowrite32(value, lp->sdma_regs + (reg << 2));
}

/* DMA register access functions can be DCR based or memory mapped.
@@ -1024,6 +1046,7 @@ static int temac_probe(struct platform_device *pdev)
struct resource *res;
const void *addr;
__be32 *p;
+ bool little_endian;
int rc = 0;

/* Init network device structure */
@@ -1068,6 +1091,24 @@ static int temac_probe(struct platform_device *pdev)
return PTR_ERR(lp->regs);
}

+ /* Select register access functions with the specified
+ * endianness mode. Default for OF devices is big-endian.
+ */
+ little_endian = false;
+ if (temac_np) {
+ if (of_get_property(temac_np, "little-endian", NULL))
+ little_endian = true;
+ } else if (pdata) {
+ little_endian = pdata->reg_little_endian;
+ }
+ if (little_endian) {
+ lp->temac_ior = _temac_ior_le;
+ lp->temac_iow = _temac_iow_le;
+ } else {
+ lp->temac_ior = _temac_ior_be;
+ lp->temac_iow = _temac_iow_be;
+ }
+
/* Setup checksum offload, but default to off if not specified */
lp->temac_features = 0;
if (temac_np) {
@@ -1111,8 +1152,13 @@ static int temac_probe(struct platform_device *pdev)
of_node_put(dma_np);
return PTR_ERR(lp->sdma_regs);
}
- lp->dma_in = temac_dma_in32;
- lp->dma_out = temac_dma_out32;
+ if (of_get_property(dma_np, "little-endian", NULL)) {
+ lp->dma_in = temac_dma_in32_le;
+ lp->dma_out = temac_dma_out32_le;
+ } else {
+ lp->dma_in = temac_dma_in32_be;
+ lp->dma_out = temac_dma_out32_be;
+ }
dev_dbg(&pdev->dev, "MEM base: %p\n", lp->sdma_regs);
}

@@ -1132,8 +1178,13 @@ static int temac_probe(struct platform_device *pdev)
"could not map DMA registers\n");
return PTR_ERR(lp->sdma_regs);
}
- lp->dma_in = temac_dma_in32;
- lp->dma_out = temac_dma_out32;
+ if (pdata->dma_little_endian) {
+ lp->dma_in = temac_dma_in32_le;
+ lp->dma_out = temac_dma_out32_le;
+ } else {
+ lp->dma_in = temac_dma_in32_be;
+ lp->dma_out = temac_dma_out32_be;
+ }

/* Get DMA RX and TX interrupts */
lp->rx_irq = platform_get_irq(pdev, 0);
diff --git a/include/linux/platform_data/xilinx-ll-temac.h b/include/linux/platform_data/xilinx-ll-temac.h
index 82e2f80..af87927 100644
--- a/include/linux/platform_data/xilinx-ll-temac.h
+++ b/include/linux/platform_data/xilinx-ll-temac.h
@@ -14,6 +14,8 @@ struct ll_temac_platform_data {
unsigned long long mdio_bus_id; /* Unique id for MDIO bus */
int phy_addr; /* Address of the PHY to connect to */
phy_interface_t phy_interface; /* PHY interface mode */
+ bool reg_little_endian; /* Little endian TEMAC register access */
+ bool dma_little_endian; /* Little endian DMA register access */
};

#endif /* __LINUX_XILINX_LL_TEMAC_H */
--
2.4.11

2019-04-29 08:39:00

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH 02/12] net: ll_temac: Extend support to non-device-tree platforms

Support initialization with platdata, so the driver can be used on
non-device-tree platforms.

For currently supported device-tree platforms, the driver should behave
as before.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac.h | 3 +
drivers/net/ethernet/xilinx/ll_temac_main.c | 187 +++++++++++++++++---------
drivers/net/ethernet/xilinx/ll_temac_mdio.c | 23 +++-
include/linux/platform_data/xilinx-ll-temac.h | 19 +++
4 files changed, 166 insertions(+), 66 deletions(-)
create mode 100644 include/linux/platform_data/xilinx-ll-temac.h

diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h
index 4557578..e338b4f 100644
--- a/drivers/net/ethernet/xilinx/ll_temac.h
+++ b/drivers/net/ethernet/xilinx/ll_temac.h
@@ -334,6 +334,9 @@ struct temac_local {

/* Connection to PHY device */
struct device_node *phy_node;
+ /* For non-device-tree devices */
+ char phy_name[MII_BUS_ID_SIZE + 3];
+ phy_interface_t phy_interface;

/* MDIO bus data */
struct mii_bus *mii_bus; /* MII bus reference */
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index c4e85a9..fddd1b3 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -33,6 +33,7 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/netdevice.h>
+#include <linux/if_ether.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
@@ -51,6 +52,7 @@
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
+#include <linux/platform_data/xilinx-ll-temac.h>

#include "ll_temac.h"

@@ -187,7 +189,7 @@ static int temac_dcr_setup(struct temac_local *lp, struct platform_device *op,

/*
* temac_dcr_setup - This is a stub for when DCR is not supported,
- * such as with MicroBlaze
+ * such as with MicroBlaze and x86
*/
static int temac_dcr_setup(struct temac_local *lp, struct platform_device *op,
struct device_node *np)
@@ -857,7 +859,14 @@ static int temac_open(struct net_device *ndev)
dev_err(lp->dev, "of_phy_connect() failed\n");
return -ENODEV;
}
-
+ phy_start(phydev);
+ } else if (strlen(lp->phy_name) > 0) {
+ phydev = phy_connect(lp->ndev, lp->phy_name, temac_adjust_link,
+ lp->phy_interface);
+ if (!phydev) {
+ dev_err(lp->dev, "phy_connect() failed\n");
+ return -ENODEV;
+ }
phy_start(phydev);
}

@@ -977,11 +986,13 @@ static const struct ethtool_ops temac_ethtool_ops = {
.set_link_ksettings = phy_ethtool_set_link_ksettings,
};

-static int temac_of_probe(struct platform_device *op)
+static int temac_probe(struct platform_device *pdev)
{
- struct device_node *np;
+ struct ll_temac_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct device_node *temac_np = dev_of_node(&pdev->dev), *dma_np;
struct temac_local *lp;
struct net_device *ndev;
+ struct resource *res;
const void *addr;
__be32 *p;
int rc = 0;
@@ -991,8 +1002,8 @@ static int temac_of_probe(struct platform_device *op)
if (!ndev)
return -ENOMEM;

- platform_set_drvdata(op, ndev);
- SET_NETDEV_DEV(ndev, &op->dev);
+ platform_set_drvdata(pdev, ndev);
+ SET_NETDEV_DEV(ndev, &pdev->dev);
ndev->flags &= ~IFF_MULTICAST; /* clear multicast */
ndev->features = NETIF_F_SG;
ndev->netdev_ops = &temac_netdev_ops;
@@ -1014,79 +1025,129 @@ static int temac_of_probe(struct platform_device *op)
/* setup temac private info structure */
lp = netdev_priv(ndev);
lp->ndev = ndev;
- lp->dev = &op->dev;
+ lp->dev = &pdev->dev;
lp->options = XTE_OPTION_DEFAULTS;
spin_lock_init(&lp->rx_lock);
mutex_init(&lp->indirect_mutex);

/* map device registers */
- lp->regs = devm_of_iomap(&op->dev, op->dev.of_node, 0, NULL);
- if (!lp->regs) {
- dev_err(&op->dev, "could not map temac regs.\n");
- return -ENOMEM;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ lp->regs = devm_ioremap_nocache(&pdev->dev, res->start,
+ resource_size(res));
+ if (IS_ERR(lp->regs)) {
+ dev_err(&pdev->dev, "could not map TEMAC registers\n");
+ return PTR_ERR(lp->regs);
}

/* Setup checksum offload, but default to off if not specified */
lp->temac_features = 0;
- p = (__be32 *)of_get_property(op->dev.of_node, "xlnx,txcsum", NULL);
- if (p && be32_to_cpu(*p)) {
- lp->temac_features |= TEMAC_FEATURE_TX_CSUM;
+ if (temac_np) {
+ p = (__be32 *)of_get_property(temac_np, "xlnx,txcsum", NULL);
+ if (p && be32_to_cpu(*p))
+ lp->temac_features |= TEMAC_FEATURE_TX_CSUM;
+ p = (__be32 *)of_get_property(temac_np, "xlnx,rxcsum", NULL);
+ if (p && be32_to_cpu(*p))
+ lp->temac_features |= TEMAC_FEATURE_RX_CSUM;
+ } else if (pdata) {
+ if (pdata->txcsum)
+ lp->temac_features |= TEMAC_FEATURE_TX_CSUM;
+ if (pdata->rxcsum)
+ lp->temac_features |= TEMAC_FEATURE_RX_CSUM;
+ }
+ if (lp->temac_features & TEMAC_FEATURE_TX_CSUM)
/* Can checksum TCP/UDP over IPv4. */
ndev->features |= NETIF_F_IP_CSUM;
- }
- p = (__be32 *)of_get_property(op->dev.of_node, "xlnx,rxcsum", NULL);
- if (p && be32_to_cpu(*p))
- lp->temac_features |= TEMAC_FEATURE_RX_CSUM;
-
- /* Find the DMA node, map the DMA registers, and decode the DMA IRQs */
- np = of_parse_phandle(op->dev.of_node, "llink-connected", 0);
- if (!np) {
- dev_err(&op->dev, "could not find DMA node\n");
- return -ENODEV;
- }

- /* Setup the DMA register accesses, could be DCR or memory mapped */
- if (temac_dcr_setup(lp, op, np)) {
+ /* Setup LocalLink DMA */
+ if (temac_np) {
+ /* Find the DMA node, map the DMA registers, and
+ * decode the DMA IRQs.
+ */
+ dma_np = of_parse_phandle(temac_np, "llink-connected", 0);
+ if (!dma_np) {
+ dev_err(&pdev->dev, "could not find DMA node\n");
+ return -ENODEV;
+ }

- /* no DCR in the device tree, try non-DCR */
- lp->sdma_regs = devm_of_iomap(&op->dev, np, 0, NULL);
- if (lp->sdma_regs) {
+ /* Setup the DMA register accesses, could be DCR or
+ * memory mapped.
+ */
+ if (temac_dcr_setup(lp, pdev, dma_np)) {
+ /* no DCR in the device tree, try non-DCR */
+ lp->sdma_regs = devm_of_iomap(&pdev->dev, dma_np, 0,
+ NULL);
+ if (IS_ERR(lp->sdma_regs)) {
+ dev_err(&pdev->dev,
+ "unable to map DMA registers\n");
+ of_node_put(dma_np);
+ return PTR_ERR(lp->sdma_regs);
+ }
lp->dma_in = temac_dma_in32;
lp->dma_out = temac_dma_out32;
- dev_dbg(&op->dev, "MEM base: %p\n", lp->sdma_regs);
- } else {
- dev_err(&op->dev, "unable to map DMA registers\n");
- of_node_put(np);
- return -ENOMEM;
+ dev_dbg(&pdev->dev, "MEM base: %p\n", lp->sdma_regs);
}
- }
-
- lp->rx_irq = irq_of_parse_and_map(np, 0);
- lp->tx_irq = irq_of_parse_and_map(np, 1);

- of_node_put(np); /* Finished with the DMA node; drop the reference */
+ /* Get DMA RX and TX interrupts */
+ lp->rx_irq = irq_of_parse_and_map(dma_np, 0);
+ lp->tx_irq = irq_of_parse_and_map(dma_np, 1);
+
+ /* Finished with the DMA node; drop the reference */
+ of_node_put(dma_np);
+ } else if (pdata) {
+ /* 2nd memory resource specifies DMA registers */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ lp->sdma_regs = devm_ioremap_nocache(&pdev->dev, res->start,
+ resource_size(res));
+ if (IS_ERR(lp->sdma_regs)) {
+ dev_err(&pdev->dev,
+ "could not map DMA registers\n");
+ return PTR_ERR(lp->sdma_regs);
+ }
+ lp->dma_in = temac_dma_in32;
+ lp->dma_out = temac_dma_out32;

- if (!lp->rx_irq || !lp->tx_irq) {
- dev_err(&op->dev, "could not determine irqs\n");
- return -ENOMEM;
+ /* Get DMA RX and TX interrupts */
+ lp->rx_irq = platform_get_irq(pdev, 0);
+ lp->tx_irq = platform_get_irq(pdev, 1);
}

+ /* Error handle returned DMA RX and TX interrupts */
+ if (lp->rx_irq < 0) {
+ if (lp->rx_irq != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "could not get DMA RX irq\n");
+ return lp->rx_irq;
+ }
+ if (lp->tx_irq < 0) {
+ if (lp->tx_irq != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "could not get DMA TX irq\n");
+ return lp->tx_irq;
+ }

- /* Retrieve the MAC address */
- addr = of_get_mac_address(op->dev.of_node);
- if (!addr) {
- dev_err(&op->dev, "could not find MAC address\n");
- return -ENODEV;
+ if (temac_np) {
+ /* Retrieve the MAC address */
+ addr = of_get_mac_address(temac_np);
+ if (!addr) {
+ dev_err(&pdev->dev, "could not find MAC address\n");
+ return -ENODEV;
+ }
+ temac_init_mac_address(ndev, addr);
+ } else if (pdata) {
+ temac_init_mac_address(ndev, pdata->mac_addr);
}
- temac_init_mac_address(ndev, addr);

rc = temac_mdio_setup(lp, pdev);
if (rc)
- dev_warn(&op->dev, "error registering MDIO bus\n");
-
- lp->phy_node = of_parse_phandle(op->dev.of_node, "phy-handle", 0);
- if (lp->phy_node)
- dev_dbg(lp->dev, "using PHY node %pOF (%p)\n", np, np);
+ dev_warn(&pdev->dev, "error registering MDIO bus\n");
+
+ if (temac_np) {
+ lp->phy_node = of_parse_phandle(temac_np, "phy-handle", 0);
+ if (lp->phy_node)
+ dev_dbg(lp->dev, "using PHY node %pOF\n", temac_np);
+ } else if (pdata) {
+ snprintf(lp->phy_name, sizeof(lp->phy_name),
+ PHY_ID_FMT, lp->mii_bus->id, pdata->phy_addr);
+ lp->phy_interface = pdata->phy_interface;
+ }

/* Add the device attributes */
rc = sysfs_create_group(&lp->dev->kobj, &temac_attr_group);
@@ -1106,19 +1167,21 @@ static int temac_of_probe(struct platform_device *op)
err_register_ndev:
sysfs_remove_group(&lp->dev->kobj, &temac_attr_group);
err_sysfs_create:
- of_node_put(lp->phy_node);
+ if (lp->phy_node)
+ of_node_put(lp->phy_node);
temac_mdio_teardown(lp);
return rc;
}

-static int temac_of_remove(struct platform_device *op)
+static int temac_remove(struct platform_device *pdev)
{
- struct net_device *ndev = platform_get_drvdata(op);
+ struct net_device *ndev = platform_get_drvdata(pdev);
struct temac_local *lp = netdev_priv(ndev);

unregister_netdev(ndev);
sysfs_remove_group(&lp->dev->kobj, &temac_attr_group);
- of_node_put(lp->phy_node);
+ if (lp->phy_node)
+ of_node_put(lp->phy_node);
temac_mdio_teardown(lp);
return 0;
}
@@ -1132,16 +1195,16 @@ static const struct of_device_id temac_of_match[] = {
};
MODULE_DEVICE_TABLE(of, temac_of_match);

-static struct platform_driver temac_of_driver = {
- .probe = temac_of_probe,
- .remove = temac_of_remove,
+static struct platform_driver temac_driver = {
+ .probe = temac_probe,
+ .remove = temac_remove,
.driver = {
.name = "xilinx_temac",
.of_match_table = temac_of_match,
},
};

-module_platform_driver(temac_of_driver);
+module_platform_driver(temac_driver);

MODULE_DESCRIPTION("Xilinx LL_TEMAC Ethernet driver");
MODULE_AUTHOR("Yoshio Kashiwagi");
diff --git a/drivers/net/ethernet/xilinx/ll_temac_mdio.c b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
index a0b365e..c5307e5 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_mdio.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
@@ -14,6 +14,7 @@
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/of_mdio.h>
+#include <linux/platform_data/xilinx-ll-temac.h>

#include "ll_temac.h"

@@ -59,6 +60,7 @@ static int temac_mdio_write(struct mii_bus *bus, int phy_id, int reg, u16 val)

int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)
{
+ struct ll_temac_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct device_node *np = dev_of_node(&pdev->dev);
struct mii_bus *bus;
u32 bus_hz;
@@ -66,9 +68,16 @@ int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)
int rc;
struct resource res;

+ /* Get MDIO bus frequency (if specified) */
+ bus_hz = 0;
+ if (np)
+ of_property_read_u32(np, "clock-frequency", &bus_hz);
+ else if (pdata)
+ bus_hz = pdata->mdio_clk_freq;
+
/* Calculate a reasonable divisor for the clock rate */
clk_div = 0x3f; /* worst-case default setting */
- if (of_property_read_u32(np, "clock-frequency", &bus_hz) == 0) {
+ if (bus_hz != 0) {
clk_div = bus_hz / (2500 * 1000 * 2) - 1;
if (clk_div < 1)
clk_div = 1;
@@ -86,9 +95,15 @@ int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)
if (!bus)
return -ENOMEM;

- of_address_to_resource(np, 0, &res);
- snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx",
- (unsigned long long)res.start);
+ if (np) {
+ of_address_to_resource(np, 0, &res);
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx",
+ (unsigned long long)res.start);
+ } else if (pdata && pdata->mdio_bus_id >= 0) {
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx",
+ pdata->mdio_bus_id);
+ }
+
bus->priv = lp;
bus->name = "Xilinx TEMAC MDIO";
bus->read = temac_mdio_read;
diff --git a/include/linux/platform_data/xilinx-ll-temac.h b/include/linux/platform_data/xilinx-ll-temac.h
new file mode 100644
index 0000000..82e2f80
--- /dev/null
+++ b/include/linux/platform_data/xilinx-ll-temac.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __LINUX_XILINX_LL_TEMAC_H
+#define __LINUX_XILINX_LL_TEMAC_H
+
+#include <linux/if_ether.h>
+#include <linux/phy.h>
+
+struct ll_temac_platform_data {
+ bool txcsum; /* Enable/disable TX checksum */
+ bool rxcsum; /* Enable/disable RX checksum */
+ u8 mac_addr[ETH_ALEN]; /* MAC address (6 bytes) */
+ /* Clock frequency for input to MDIO clock generator */
+ u32 mdio_clk_freq;
+ unsigned long long mdio_bus_id; /* Unique id for MDIO bus */
+ int phy_addr; /* Address of the PHY to connect to */
+ phy_interface_t phy_interface; /* PHY interface mode */
+};
+
+#endif /* __LINUX_XILINX_LL_TEMAC_H */
--
2.4.11

2019-04-29 22:08:05

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH 03/12] net: ll_temac: Fix support for 64-bit platforms

On Mon, Apr 29, 2019 at 10:34:13AM +0200, Esben Haabendal wrote:
> The use of buffer descriptor APP4 field (32-bit) for storing skb pointer
> obviously does not work on 64-bit platforms.
> As APP3 is also unused, we can use that to store the other half of 64-bit
> pointer values.
>
> Contrary to what is hinted at in commit message of commit 15bfe05c8d63
> ("net: ethernet: xilinx: Mark XILINX_LL_TEMAC broken on 64-bit")
> there are no other pointers stored in cdmac_bd.
>
> Signed-off-by: Esben Haabendal <[email protected]>

Reviewed-by: Andrew Lunn <[email protected]>

Andrew

2019-04-29 22:09:52

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH 06/12] net: ll_temac: Allow use on x86 platforms

On Mon, Apr 29, 2019 at 10:34:16AM +0200, Esben Haabendal wrote:
> With little-endian and 64-bit support in place, the ll_temac driver can
> now be used on x86 and x86_64 platforms.
>
> And while at it, enable COMPILE_TEST also.
>
> Signed-off-by: Esben Haabendal <[email protected]>

Reviewed-by: Andrew Lunn <[email protected]>

Andrew

2019-04-29 22:13:26

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH 07/12] net: ll_temac: Support indirect_mutex share within TEMAC IP

> For OF devices, the xlnx,compound parent of the temac node should be
> used to find siblings, and setup a shared indirect_mutex between them.
> I will leave this work to somebody else, as I don't have hardware to
> test that. No regression is introduced by that, as before this commit
> using two Ethernet interfaces in same TEMAC block is simply broken.

Is that true?

> @@ -1092,7 +1092,16 @@ static int temac_probe(struct platform_device *pdev)
> lp->dev = &pdev->dev;
> lp->options = XTE_OPTION_DEFAULTS;
> spin_lock_init(&lp->rx_lock);
> - mutex_init(&lp->indirect_mutex);
> +
> + /* Setup mutex for synchronization of indirect register access */
> + if (pdata) {
> + if (!pdata->indirect_mutex) {
> + dev_err(&pdev->dev,
> + "indirect_mutex missing in platform_data\n");
> + return -EINVAL;
> + }
> + lp->indirect_mutex = pdata->indirect_mutex;
> + }

In the OF case, isn't lp->indirect_mutex now a NULL pointer, where as
before it was a valid mutex?

Or did i miss something somewhere?

Andrew

2019-04-30 06:55:33

by Esben Haabendal

[permalink] [raw]
Subject: Re: [PATCH 07/12] net: ll_temac: Support indirect_mutex share within TEMAC IP

Andrew Lunn <[email protected]> writes:

>> For OF devices, the xlnx,compound parent of the temac node should be
>> used to find siblings, and setup a shared indirect_mutex between them.
>> I will leave this work to somebody else, as I don't have hardware to
>> test that. No regression is introduced by that, as before this commit
>> using two Ethernet interfaces in same TEMAC block is simply broken.
>
> Is that true?

Ouch, it was in v1. But I messed up here in v2. I will fix for v3.

>> @@ -1092,7 +1092,16 @@ static int temac_probe(struct platform_device *pdev)
>> lp->dev = &pdev->dev;
>> lp->options = XTE_OPTION_DEFAULTS;
>> spin_lock_init(&lp->rx_lock);
>> - mutex_init(&lp->indirect_mutex);
>> +
>> + /* Setup mutex for synchronization of indirect register access */
>> + if (pdata) {
>> + if (!pdata->indirect_mutex) {
>> + dev_err(&pdev->dev,
>> + "indirect_mutex missing in platform_data\n");
>> + return -EINVAL;
>> + }
>> + lp->indirect_mutex = pdata->indirect_mutex;
>> + }
>
> In the OF case, isn't lp->indirect_mutex now a NULL pointer, where as
> before it was a valid mutex?
>
> Or did i miss something somewhere?

No, you did not miss something. But I did messed up the OF case in v2
of this series. Sorry.

/Esben

2019-04-30 07:19:14

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH v3 00/12] net: ll_temac: x86_64 support

This patch series adds support for use of ll_temac driver with
platform_data configuration and fixes endianess and 64-bit problems so
that it can be used on x86_64 platform.

A few bugfixes are also included.

Changes since v2:
- Fixed lp->indirect_mutex initialization regression for OF
platforms introduced in v2

Changes since v1:
- Make indirect_mutex specification mandatory when using platform_data
- Move header to include/linux/platform_data
- Enable COMPILE_TEST for XILINX_LL_TEMAC
- Rebased to v5.1-rc7


Esben Haabendal (12):
net: ll_temac: Fix and simplify error handling by using devres
functions
net: ll_temac: Extend support to non-device-tree platforms
net: ll_temac: Fix support for 64-bit platforms
net: ll_temac: Add support for non-native register endianness
net: ll_temac: Fix support for little-endian platforms
net: ll_temac: Allow use on x86 platforms
net: ll_temac: Support indirect_mutex share within TEMAC IP
net: ll_temac: Fix iommu/swiotlb leak
net: ll_temac: Fix bug causing buffer descriptor overrun
net: ll_temac: Replace bad usage of msleep() with usleep_range()
net: ll_temac: Allow configuration of IRQ coalescing
net: ll_temac: Enable DMA when ready, not before

drivers/net/ethernet/xilinx/Kconfig | 5 +-
drivers/net/ethernet/xilinx/ll_temac.h | 26 +-
drivers/net/ethernet/xilinx/ll_temac_main.c | 519 +++++++++++++++++---------
drivers/net/ethernet/xilinx/ll_temac_mdio.c | 53 +--
include/linux/platform_data/xilinx-ll-temac.h | 32 ++
5 files changed, 432 insertions(+), 203 deletions(-)
create mode 100644 include/linux/platform_data/xilinx-ll-temac.h

--
2.4.11

2019-04-30 07:19:26

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH v3 01/12] net: ll_temac: Fix and simplify error handling by using devres functions

As a side effect, a few error cases are fixed.

If of_iomap() of sdma_regs failed, no error code was returned. Fixed to
return -ENOMEM similar to of_iomap() fail of regs.

If sysfs_create_group() or register_netdev() failed, lp->phy_node was not
released.

Finally, the order in remove function is corrected to be reverse order
of what is done in probe, i.e. calling temac_mdio_teardown() last, so we
unregister the netdev that most likely is using the mdio_bus first.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac.h | 2 +-
drivers/net/ethernet/xilinx/ll_temac_main.c | 48 ++++++++++-------------------
drivers/net/ethernet/xilinx/ll_temac_mdio.c | 14 +++------
3 files changed, 22 insertions(+), 42 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h
index 1075752..4557578 100644
--- a/drivers/net/ethernet/xilinx/ll_temac.h
+++ b/drivers/net/ethernet/xilinx/ll_temac.h
@@ -378,7 +378,7 @@ void temac_indirect_out32(struct temac_local *lp, int reg, u32 value);


/* xilinx_temac_mdio.c */
-int temac_mdio_setup(struct temac_local *lp, struct device_node *np);
+int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev);
void temac_mdio_teardown(struct temac_local *lp);

#endif /* XILINX_LL_TEMAC_H */
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 44efffb..c4e85a9 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -225,7 +225,6 @@ static void temac_dma_bd_release(struct net_device *ndev)
dma_free_coherent(ndev->dev.parent,
sizeof(*lp->tx_bd_v) * TX_BD_NUM,
lp->tx_bd_v, lp->tx_bd_p);
- kfree(lp->rx_skb);
}

/**
@@ -237,7 +236,8 @@ static int temac_dma_bd_init(struct net_device *ndev)
struct sk_buff *skb;
int i;

- lp->rx_skb = kcalloc(RX_BD_NUM, sizeof(*lp->rx_skb), GFP_KERNEL);
+ lp->rx_skb = devm_kcalloc(&ndev->dev, RX_BD_NUM, sizeof(*lp->rx_skb),
+ GFP_KERNEL);
if (!lp->rx_skb)
goto out;

@@ -987,7 +987,7 @@ static int temac_of_probe(struct platform_device *op)
int rc = 0;

/* Init network device structure */
- ndev = alloc_etherdev(sizeof(*lp));
+ ndev = devm_alloc_etherdev(&pdev->dev, sizeof(*lp));
if (!ndev)
return -ENOMEM;

@@ -1020,11 +1020,10 @@ static int temac_of_probe(struct platform_device *op)
mutex_init(&lp->indirect_mutex);

/* map device registers */
- lp->regs = of_iomap(op->dev.of_node, 0);
+ lp->regs = devm_of_iomap(&op->dev, op->dev.of_node, 0, NULL);
if (!lp->regs) {
dev_err(&op->dev, "could not map temac regs.\n");
- rc = -ENOMEM;
- goto nodev;
+ return -ENOMEM;
}

/* Setup checksum offload, but default to off if not specified */
@@ -1043,15 +1042,14 @@ static int temac_of_probe(struct platform_device *op)
np = of_parse_phandle(op->dev.of_node, "llink-connected", 0);
if (!np) {
dev_err(&op->dev, "could not find DMA node\n");
- rc = -ENODEV;
- goto err_iounmap;
+ return -ENODEV;
}

/* Setup the DMA register accesses, could be DCR or memory mapped */
if (temac_dcr_setup(lp, op, np)) {

/* no DCR in the device tree, try non-DCR */
- lp->sdma_regs = of_iomap(np, 0);
+ lp->sdma_regs = devm_of_iomap(&op->dev, np, 0, NULL);
if (lp->sdma_regs) {
lp->dma_in = temac_dma_in32;
lp->dma_out = temac_dma_out32;
@@ -1059,7 +1057,7 @@ static int temac_of_probe(struct platform_device *op)
} else {
dev_err(&op->dev, "unable to map DMA registers\n");
of_node_put(np);
- goto err_iounmap;
+ return -ENOMEM;
}
}

@@ -1070,8 +1068,7 @@ static int temac_of_probe(struct platform_device *op)

if (!lp->rx_irq || !lp->tx_irq) {
dev_err(&op->dev, "could not determine irqs\n");
- rc = -ENOMEM;
- goto err_iounmap_2;
+ return -ENOMEM;
}


@@ -1079,12 +1076,11 @@ static int temac_of_probe(struct platform_device *op)
addr = of_get_mac_address(op->dev.of_node);
if (!addr) {
dev_err(&op->dev, "could not find MAC address\n");
- rc = -ENODEV;
- goto err_iounmap_2;
+ return -ENODEV;
}
temac_init_mac_address(ndev, addr);

- rc = temac_mdio_setup(lp, op->dev.of_node);
+ rc = temac_mdio_setup(lp, pdev);
if (rc)
dev_warn(&op->dev, "error registering MDIO bus\n");

@@ -1096,7 +1092,7 @@ static int temac_of_probe(struct platform_device *op)
rc = sysfs_create_group(&lp->dev->kobj, &temac_attr_group);
if (rc) {
dev_err(lp->dev, "Error creating sysfs files\n");
- goto err_iounmap_2;
+ goto err_sysfs_create;
}

rc = register_netdev(lp->ndev);
@@ -1107,16 +1103,11 @@ static int temac_of_probe(struct platform_device *op)

return 0;

- err_register_ndev:
+err_register_ndev:
sysfs_remove_group(&lp->dev->kobj, &temac_attr_group);
- err_iounmap_2:
- if (lp->sdma_regs)
- iounmap(lp->sdma_regs);
- err_iounmap:
- iounmap(lp->regs);
- nodev:
- free_netdev(ndev);
- ndev = NULL;
+err_sysfs_create:
+ of_node_put(lp->phy_node);
+ temac_mdio_teardown(lp);
return rc;
}

@@ -1125,15 +1116,10 @@ static int temac_of_remove(struct platform_device *op)
struct net_device *ndev = platform_get_drvdata(op);
struct temac_local *lp = netdev_priv(ndev);

- temac_mdio_teardown(lp);
unregister_netdev(ndev);
sysfs_remove_group(&lp->dev->kobj, &temac_attr_group);
of_node_put(lp->phy_node);
- lp->phy_node = NULL;
- iounmap(lp->regs);
- if (lp->sdma_regs)
- iounmap(lp->sdma_regs);
- free_netdev(ndev);
+ temac_mdio_teardown(lp);
return 0;
}

diff --git a/drivers/net/ethernet/xilinx/ll_temac_mdio.c b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
index f5e83ac..a0b365e 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_mdio.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
@@ -57,8 +57,9 @@ static int temac_mdio_write(struct mii_bus *bus, int phy_id, int reg, u16 val)
return 0;
}

-int temac_mdio_setup(struct temac_local *lp, struct device_node *np)
+int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)
{
+ struct device_node *np = dev_of_node(&pdev->dev);
struct mii_bus *bus;
u32 bus_hz;
int clk_div;
@@ -81,7 +82,7 @@ int temac_mdio_setup(struct temac_local *lp, struct device_node *np)
temac_indirect_out32(lp, XTE_MC_OFFSET, 1 << 6 | clk_div);
mutex_unlock(&lp->indirect_mutex);

- bus = mdiobus_alloc();
+ bus = devm_mdiobus_alloc(&pdev->dev);
if (!bus)
return -ENOMEM;

@@ -98,23 +99,16 @@ int temac_mdio_setup(struct temac_local *lp, struct device_node *np)

rc = of_mdiobus_register(bus, np);
if (rc)
- goto err_register;
+ return rc;

mutex_lock(&lp->indirect_mutex);
dev_dbg(lp->dev, "MDIO bus registered; MC:%x\n",
temac_indirect_in32(lp, XTE_MC_OFFSET));
mutex_unlock(&lp->indirect_mutex);
return 0;
-
- err_register:
- mdiobus_free(bus);
- return rc;
}

void temac_mdio_teardown(struct temac_local *lp)
{
mdiobus_unregister(lp->mii_bus);
- mdiobus_free(lp->mii_bus);
- lp->mii_bus = NULL;
}
-
--
2.4.11

2019-04-30 07:19:44

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH v3 03/12] net: ll_temac: Fix support for 64-bit platforms

The use of buffer descriptor APP4 field (32-bit) for storing skb pointer
obviously does not work on 64-bit platforms.
As APP3 is also unused, we can use that to store the other half of 64-bit
pointer values.

Contrary to what is hinted at in commit message of commit 15bfe05c8d63
("net: ethernet: xilinx: Mark XILINX_LL_TEMAC broken on 64-bit")
there are no other pointers stored in cdmac_bd.

Signed-off-by: Esben Haabendal <[email protected]>
Reviewed-by: Andrew Lunn <[email protected]>
---
drivers/net/ethernet/xilinx/Kconfig | 1 -
drivers/net/ethernet/xilinx/ll_temac_main.c | 35 ++++++++++++++++++++++++++---
2 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig
index da4ec57..6d68c8a 100644
--- a/drivers/net/ethernet/xilinx/Kconfig
+++ b/drivers/net/ethernet/xilinx/Kconfig
@@ -34,7 +34,6 @@ config XILINX_AXI_EMAC
config XILINX_LL_TEMAC
tristate "Xilinx LL TEMAC (LocalLink Tri-mode Ethernet MAC) driver"
depends on (PPC || MICROBLAZE)
- depends on !64BIT || BROKEN
select PHYLIB
---help---
This driver supports the Xilinx 10/100/1000 LocalLink TEMAC
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index fddd1b3..bcafb89 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -619,11 +619,39 @@ static void temac_adjust_link(struct net_device *ndev)
mutex_unlock(&lp->indirect_mutex);
}

+#ifdef CONFIG_64BIT
+
+void ptr_to_txbd(void *p, struct cdmac_bd *bd)
+{
+ bd->app3 = (u32)(((u64)p) >> 32);
+ bd->app4 = (u32)((u64)p & 0xFFFFFFFF);
+}
+
+void *ptr_from_txbd(struct cdmac_bd *bd)
+{
+ return (void *)(((u64)(bd->app3) << 32) | bd->app4);
+}
+
+#else
+
+void ptr_to_txbd(void *p, struct cmdac_bd *bd)
+{
+ bd->app4 = (u32)p;
+}
+
+void *ptr_from_txbd(struct cdmac_bd *bd)
+{
+ return (void *)(bd->app4);
+}
+
+#endif
+
static void temac_start_xmit_done(struct net_device *ndev)
{
struct temac_local *lp = netdev_priv(ndev);
struct cdmac_bd *cur_p;
unsigned int stat = 0;
+ struct sk_buff *skb;

cur_p = &lp->tx_bd_v[lp->tx_bd_ci];
stat = cur_p->app0;
@@ -631,8 +659,9 @@ static void temac_start_xmit_done(struct net_device *ndev)
while (stat & STS_CTRL_APP0_CMPLT) {
dma_unmap_single(ndev->dev.parent, cur_p->phys, cur_p->len,
DMA_TO_DEVICE);
- if (cur_p->app4)
- dev_consume_skb_irq((struct sk_buff *)cur_p->app4);
+ skb = (struct sk_buff *)ptr_from_txbd(cur_p);
+ if (skb)
+ dev_consume_skb_irq(skb);
cur_p->app0 = 0;
cur_p->app1 = 0;
cur_p->app2 = 0;
@@ -711,7 +740,7 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
cur_p->len = skb_headlen(skb);
cur_p->phys = dma_map_single(ndev->dev.parent, skb->data,
skb_headlen(skb), DMA_TO_DEVICE);
- cur_p->app4 = (unsigned long)skb;
+ ptr_to_txbd((void *)skb, cur_p);

for (ii = 0; ii < num_frag; ii++) {
lp->tx_bd_tail++;
--
2.4.11

2019-04-30 07:19:53

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH v3 04/12] net: ll_temac: Add support for non-native register endianness

Replace the powerpc specific MMIO register access functions with the
generic big-endian mmio access functions, and add support for
little-endian access depending on configuration.

Big-endian access is maintained as the default, but little-endian can
be configured in device-tree binding or in platform data.

The temac_ior()/temac_iow() functions are replaced with macro wrappers
to avoid modifying existing code more than necessary.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac.h | 12 ++--
drivers/net/ethernet/xilinx/ll_temac_main.c | 87 +++++++++++++++++++++------
include/linux/platform_data/xilinx-ll-temac.h | 2 +
3 files changed, 79 insertions(+), 22 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h
index e338b4f..23d8dd5 100644
--- a/drivers/net/ethernet/xilinx/ll_temac.h
+++ b/drivers/net/ethernet/xilinx/ll_temac.h
@@ -347,8 +347,10 @@ struct temac_local {
#ifdef CONFIG_PPC_DCR
dcr_host_t sdma_dcrs;
#endif
- u32 (*dma_in)(struct temac_local *, int);
- void (*dma_out)(struct temac_local *, int, u32);
+ u32 (*temac_ior)(struct temac_local *lp, int offset);
+ void (*temac_iow)(struct temac_local *lp, int offset, u32 value);
+ u32 (*dma_in)(struct temac_local *lp, int reg);
+ void (*dma_out)(struct temac_local *lp, int reg, u32 value);

int tx_irq;
int rx_irq;
@@ -372,9 +374,11 @@ struct temac_local {
int rx_bd_ci;
};

+/* Wrappers for temac_ior()/temac_iow() function pointers above */
+#define temac_ior(lp, o) ((lp)->temac_ior(lp, o))
+#define temac_iow(lp, o, v) ((lp)->temac_iow(lp, o, v))
+
/* xilinx_temac.c */
-u32 temac_ior(struct temac_local *lp, int offset);
-void temac_iow(struct temac_local *lp, int offset, u32 value);
int temac_indirect_busywait(struct temac_local *lp);
u32 temac_indirect_in32(struct temac_local *lp, int reg);
void temac_indirect_out32(struct temac_local *lp, int reg, u32 value);
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index bcafb89..58c6713 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -63,14 +63,24 @@
* Low level register access functions
*/

-u32 temac_ior(struct temac_local *lp, int offset)
+u32 _temac_ior_be(struct temac_local *lp, int offset)
{
- return in_be32(lp->regs + offset);
+ return ioread32be(lp->regs + offset);
}

-void temac_iow(struct temac_local *lp, int offset, u32 value)
+void _temac_iow_be(struct temac_local *lp, int offset, u32 value)
{
- out_be32(lp->regs + offset, value);
+ return iowrite32be(value, lp->regs + offset);
+}
+
+u32 _temac_ior_le(struct temac_local *lp, int offset)
+{
+ return ioread32(lp->regs + offset);
+}
+
+void _temac_iow_le(struct temac_local *lp, int offset, u32 value)
+{
+ return iowrite32(value, lp->regs + offset);
}

int temac_indirect_busywait(struct temac_local *lp)
@@ -121,23 +131,35 @@ void temac_indirect_out32(struct temac_local *lp, int reg, u32 value)
}

/**
- * temac_dma_in32 - Memory mapped DMA read, this function expects a
- * register input that is based on DCR word addresses which
- * are then converted to memory mapped byte addresses
+ * temac_dma_in32_* - Memory mapped DMA read, these function expects a
+ * register input that is based on DCR word addresses which are then
+ * converted to memory mapped byte addresses. To be assigned to
+ * lp->dma_in32.
*/
-static u32 temac_dma_in32(struct temac_local *lp, int reg)
+static u32 temac_dma_in32_be(struct temac_local *lp, int reg)
{
- return in_be32(lp->sdma_regs + (reg << 2));
+ return ioread32be(lp->sdma_regs + (reg << 2));
+}
+
+static u32 temac_dma_in32_le(struct temac_local *lp, int reg)
+{
+ return ioread32(lp->sdma_regs + (reg << 2));
}

/**
- * temac_dma_out32 - Memory mapped DMA read, this function expects a
- * register input that is based on DCR word addresses which
- * are then converted to memory mapped byte addresses
+ * temac_dma_out32_* - Memory mapped DMA read, these function expects
+ * a register input that is based on DCR word addresses which are then
+ * converted to memory mapped byte addresses. To be assigned to
+ * lp->dma_out32.
*/
-static void temac_dma_out32(struct temac_local *lp, int reg, u32 value)
+static void temac_dma_out32_be(struct temac_local *lp, int reg, u32 value)
+{
+ iowrite32be(value, lp->sdma_regs + (reg << 2));
+}
+
+static void temac_dma_out32_le(struct temac_local *lp, int reg, u32 value)
{
- out_be32(lp->sdma_regs + (reg << 2), value);
+ iowrite32(value, lp->sdma_regs + (reg << 2));
}

/* DMA register access functions can be DCR based or memory mapped.
@@ -1024,6 +1046,7 @@ static int temac_probe(struct platform_device *pdev)
struct resource *res;
const void *addr;
__be32 *p;
+ bool little_endian;
int rc = 0;

/* Init network device structure */
@@ -1068,6 +1091,24 @@ static int temac_probe(struct platform_device *pdev)
return PTR_ERR(lp->regs);
}

+ /* Select register access functions with the specified
+ * endianness mode. Default for OF devices is big-endian.
+ */
+ little_endian = false;
+ if (temac_np) {
+ if (of_get_property(temac_np, "little-endian", NULL))
+ little_endian = true;
+ } else if (pdata) {
+ little_endian = pdata->reg_little_endian;
+ }
+ if (little_endian) {
+ lp->temac_ior = _temac_ior_le;
+ lp->temac_iow = _temac_iow_le;
+ } else {
+ lp->temac_ior = _temac_ior_be;
+ lp->temac_iow = _temac_iow_be;
+ }
+
/* Setup checksum offload, but default to off if not specified */
lp->temac_features = 0;
if (temac_np) {
@@ -1111,8 +1152,13 @@ static int temac_probe(struct platform_device *pdev)
of_node_put(dma_np);
return PTR_ERR(lp->sdma_regs);
}
- lp->dma_in = temac_dma_in32;
- lp->dma_out = temac_dma_out32;
+ if (of_get_property(dma_np, "little-endian", NULL)) {
+ lp->dma_in = temac_dma_in32_le;
+ lp->dma_out = temac_dma_out32_le;
+ } else {
+ lp->dma_in = temac_dma_in32_be;
+ lp->dma_out = temac_dma_out32_be;
+ }
dev_dbg(&pdev->dev, "MEM base: %p\n", lp->sdma_regs);
}

@@ -1132,8 +1178,13 @@ static int temac_probe(struct platform_device *pdev)
"could not map DMA registers\n");
return PTR_ERR(lp->sdma_regs);
}
- lp->dma_in = temac_dma_in32;
- lp->dma_out = temac_dma_out32;
+ if (pdata->dma_little_endian) {
+ lp->dma_in = temac_dma_in32_le;
+ lp->dma_out = temac_dma_out32_le;
+ } else {
+ lp->dma_in = temac_dma_in32_be;
+ lp->dma_out = temac_dma_out32_be;
+ }

/* Get DMA RX and TX interrupts */
lp->rx_irq = platform_get_irq(pdev, 0);
diff --git a/include/linux/platform_data/xilinx-ll-temac.h b/include/linux/platform_data/xilinx-ll-temac.h
index 82e2f80..af87927 100644
--- a/include/linux/platform_data/xilinx-ll-temac.h
+++ b/include/linux/platform_data/xilinx-ll-temac.h
@@ -14,6 +14,8 @@ struct ll_temac_platform_data {
unsigned long long mdio_bus_id; /* Unique id for MDIO bus */
int phy_addr; /* Address of the PHY to connect to */
phy_interface_t phy_interface; /* PHY interface mode */
+ bool reg_little_endian; /* Little endian TEMAC register access */
+ bool dma_little_endian; /* Little endian DMA register access */
};

#endif /* __LINUX_XILINX_LL_TEMAC_H */
--
2.4.11

2019-04-30 07:20:04

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH v3 05/12] net: ll_temac: Fix support for little-endian platforms

Both TEMAC and SDMA is big-endian, so make sure that all values in SDMA
buffer descriptors (cmdac_bd) are handled as big-endian, independent of the
host endianness. With all currently supported platforms being big-endian,
this change does not make a change for any of them.

Note, when using app3 and app4 for piggybacking skb pointers there is no
need to care about endianness, as neither TEMAC nor SDMA access app3 and
app4 in TX buffer descriptors.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac_main.c | 89 ++++++++++++++++-------------
1 file changed, 50 insertions(+), 39 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 58c6713..179a998 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -258,6 +258,7 @@ static int temac_dma_bd_init(struct net_device *ndev)
{
struct temac_local *lp = netdev_priv(ndev);
struct sk_buff *skb;
+ dma_addr_t skb_dma_addr;
int i;

lp->rx_skb = devm_kcalloc(&ndev->dev, RX_BD_NUM, sizeof(*lp->rx_skb),
@@ -280,13 +281,13 @@ static int temac_dma_bd_init(struct net_device *ndev)
goto out;

for (i = 0; i < TX_BD_NUM; i++) {
- lp->tx_bd_v[i].next = lp->tx_bd_p +
- sizeof(*lp->tx_bd_v) * ((i + 1) % TX_BD_NUM);
+ lp->tx_bd_v[i].next = cpu_to_be32(lp->tx_bd_p
+ + sizeof(*lp->tx_bd_v) * ((i + 1) % TX_BD_NUM));
}

for (i = 0; i < RX_BD_NUM; i++) {
- lp->rx_bd_v[i].next = lp->rx_bd_p +
- sizeof(*lp->rx_bd_v) * ((i + 1) % RX_BD_NUM);
+ lp->rx_bd_v[i].next = cpu_to_be32(lp->rx_bd_p
+ + sizeof(*lp->rx_bd_v) * ((i + 1) % RX_BD_NUM));

skb = netdev_alloc_skb_ip_align(ndev,
XTE_MAX_JUMBO_FRAME_SIZE);
@@ -295,12 +296,12 @@ static int temac_dma_bd_init(struct net_device *ndev)

lp->rx_skb[i] = skb;
/* returns physical address of skb->data */
- lp->rx_bd_v[i].phys = dma_map_single(ndev->dev.parent,
- skb->data,
- XTE_MAX_JUMBO_FRAME_SIZE,
- DMA_FROM_DEVICE);
- lp->rx_bd_v[i].len = XTE_MAX_JUMBO_FRAME_SIZE;
- lp->rx_bd_v[i].app0 = STS_CTRL_APP0_IRQONEND;
+ skb_dma_addr = dma_map_single(ndev->dev.parent, skb->data,
+ XTE_MAX_JUMBO_FRAME_SIZE,
+ DMA_FROM_DEVICE);
+ lp->rx_bd_v[i].phys = cpu_to_be32(skb_dma_addr);
+ lp->rx_bd_v[i].len = cpu_to_be32(XTE_MAX_JUMBO_FRAME_SIZE);
+ lp->rx_bd_v[i].app0 = cpu_to_be32(STS_CTRL_APP0_IRQONEND);
}

lp->dma_out(lp, TX_CHNL_CTRL, 0x10220400 |
@@ -676,11 +677,11 @@ static void temac_start_xmit_done(struct net_device *ndev)
struct sk_buff *skb;

cur_p = &lp->tx_bd_v[lp->tx_bd_ci];
- stat = cur_p->app0;
+ stat = be32_to_cpu(cur_p->app0);

while (stat & STS_CTRL_APP0_CMPLT) {
- dma_unmap_single(ndev->dev.parent, cur_p->phys, cur_p->len,
- DMA_TO_DEVICE);
+ dma_unmap_single(ndev->dev.parent, be32_to_cpu(cur_p->phys),
+ be32_to_cpu(cur_p->len), DMA_TO_DEVICE);
skb = (struct sk_buff *)ptr_from_txbd(cur_p);
if (skb)
dev_consume_skb_irq(skb);
@@ -691,14 +692,14 @@ static void temac_start_xmit_done(struct net_device *ndev)
cur_p->app4 = 0;

ndev->stats.tx_packets++;
- ndev->stats.tx_bytes += cur_p->len;
+ ndev->stats.tx_bytes += be32_to_cpu(cur_p->len);

lp->tx_bd_ci++;
if (lp->tx_bd_ci >= TX_BD_NUM)
lp->tx_bd_ci = 0;

cur_p = &lp->tx_bd_v[lp->tx_bd_ci];
- stat = cur_p->app0;
+ stat = be32_to_cpu(cur_p->app0);
}

netif_wake_queue(ndev);
@@ -732,7 +733,7 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
{
struct temac_local *lp = netdev_priv(ndev);
struct cdmac_bd *cur_p;
- dma_addr_t start_p, tail_p;
+ dma_addr_t start_p, tail_p, skb_dma_addr;
int ii;
unsigned long num_frag;
skb_frag_t *frag;
@@ -753,15 +754,17 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
unsigned int csum_start_off = skb_checksum_start_offset(skb);
unsigned int csum_index_off = csum_start_off + skb->csum_offset;

- cur_p->app0 |= 1; /* TX Checksum Enabled */
- cur_p->app1 = (csum_start_off << 16) | csum_index_off;
+ cur_p->app0 |= cpu_to_be32(0x000001); /* TX Checksum Enabled */
+ cur_p->app1 = cpu_to_be32((csum_start_off << 16)
+ | csum_index_off);
cur_p->app2 = 0; /* initial checksum seed */
}

- cur_p->app0 |= STS_CTRL_APP0_SOP;
- cur_p->len = skb_headlen(skb);
- cur_p->phys = dma_map_single(ndev->dev.parent, skb->data,
- skb_headlen(skb), DMA_TO_DEVICE);
+ cur_p->app0 |= cpu_to_be32(STS_CTRL_APP0_SOP);
+ skb_dma_addr = dma_map_single(ndev->dev.parent, skb->data,
+ skb_headlen(skb), DMA_TO_DEVICE);
+ cur_p->len = cpu_to_be32(skb_headlen(skb));
+ cur_p->phys = cpu_to_be32(skb_dma_addr);
ptr_to_txbd((void *)skb, cur_p);

for (ii = 0; ii < num_frag; ii++) {
@@ -770,14 +773,16 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
lp->tx_bd_tail = 0;

cur_p = &lp->tx_bd_v[lp->tx_bd_tail];
- cur_p->phys = dma_map_single(ndev->dev.parent,
- skb_frag_address(frag),
- skb_frag_size(frag), DMA_TO_DEVICE);
- cur_p->len = skb_frag_size(frag);
+ skb_dma_addr = dma_map_single(ndev->dev.parent,
+ skb_frag_address(frag),
+ skb_frag_size(frag),
+ DMA_TO_DEVICE);
+ cur_p->phys = cpu_to_be32(skb_dma_addr);
+ cur_p->len = cpu_to_be32(skb_frag_size(frag));
cur_p->app0 = 0;
frag++;
}
- cur_p->app0 |= STS_CTRL_APP0_EOP;
+ cur_p->app0 |= cpu_to_be32(STS_CTRL_APP0_EOP);

tail_p = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * lp->tx_bd_tail;
lp->tx_bd_tail++;
@@ -799,7 +804,7 @@ static void ll_temac_recv(struct net_device *ndev)
struct sk_buff *skb, *new_skb;
unsigned int bdstat;
struct cdmac_bd *cur_p;
- dma_addr_t tail_p;
+ dma_addr_t tail_p, skb_dma_addr;
int length;
unsigned long flags;

@@ -808,14 +813,14 @@ static void ll_temac_recv(struct net_device *ndev)
tail_p = lp->rx_bd_p + sizeof(*lp->rx_bd_v) * lp->rx_bd_ci;
cur_p = &lp->rx_bd_v[lp->rx_bd_ci];

- bdstat = cur_p->app0;
+ bdstat = be32_to_cpu(cur_p->app0);
while ((bdstat & STS_CTRL_APP0_CMPLT)) {

skb = lp->rx_skb[lp->rx_bd_ci];
- length = cur_p->app4 & 0x3FFF;
+ length = be32_to_cpu(cur_p->app4) & 0x3FFF;

- dma_unmap_single(ndev->dev.parent, cur_p->phys, length,
- DMA_FROM_DEVICE);
+ dma_unmap_single(ndev->dev.parent, be32_to_cpu(cur_p->phys),
+ length, DMA_FROM_DEVICE);

skb_put(skb, length);
skb->protocol = eth_type_trans(skb, ndev);
@@ -826,7 +831,12 @@ static void ll_temac_recv(struct net_device *ndev)
(skb->protocol == htons(ETH_P_IP)) &&
(skb->len > 64)) {

- skb->csum = cur_p->app3 & 0xFFFF;
+ /* Convert from device endianness (be32) to cpu
+ * endiannes, and if necessary swap the bytes
+ * (back) for proper IP checksum byte order
+ * (be16).
+ */
+ skb->csum = htons(be32_to_cpu(cur_p->app3) & 0xFFFF);
skb->ip_summed = CHECKSUM_COMPLETE;
}

@@ -843,11 +853,12 @@ static void ll_temac_recv(struct net_device *ndev)
return;
}

- cur_p->app0 = STS_CTRL_APP0_IRQONEND;
- cur_p->phys = dma_map_single(ndev->dev.parent, new_skb->data,
- XTE_MAX_JUMBO_FRAME_SIZE,
- DMA_FROM_DEVICE);
- cur_p->len = XTE_MAX_JUMBO_FRAME_SIZE;
+ cur_p->app0 = cpu_to_be32(STS_CTRL_APP0_IRQONEND);
+ skb_dma_addr = dma_map_single(ndev->dev.parent, new_skb->data,
+ XTE_MAX_JUMBO_FRAME_SIZE,
+ DMA_FROM_DEVICE);
+ cur_p->phys = cpu_to_be32(skb_dma_addr);
+ cur_p->len = cpu_to_be32(XTE_MAX_JUMBO_FRAME_SIZE);
lp->rx_skb[lp->rx_bd_ci] = new_skb;

lp->rx_bd_ci++;
@@ -855,7 +866,7 @@ static void ll_temac_recv(struct net_device *ndev)
lp->rx_bd_ci = 0;

cur_p = &lp->rx_bd_v[lp->rx_bd_ci];
- bdstat = cur_p->app0;
+ bdstat = be32_to_cpu(cur_p->app0);
}
lp->dma_out(lp, RX_TAILDESC_PTR, tail_p);

--
2.4.11

2019-04-30 07:20:18

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH v3 08/12] net: ll_temac: Fix iommu/swiotlb leak

Unmap the actual buffer length, not the amount of data received, avoiding
resource exhaustion of swiotlb (seen on x86_64 platform).

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac_main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index d3899e7..7e42746 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -820,7 +820,7 @@ static void ll_temac_recv(struct net_device *ndev)
length = be32_to_cpu(cur_p->app4) & 0x3FFF;

dma_unmap_single(ndev->dev.parent, be32_to_cpu(cur_p->phys),
- length, DMA_FROM_DEVICE);
+ XTE_MAX_JUMBO_FRAME_SIZE, DMA_FROM_DEVICE);

skb_put(skb, length);
skb->protocol = eth_type_trans(skb, ndev);
--
2.4.11

2019-04-30 07:20:30

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH v3 12/12] net: ll_temac: Enable DMA when ready, not before

As soon as TAILDESCR_PTR is written, DMA transfers might start.
Let's ensure we are ready to receive DMA IRQ's before doing that.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac_main.c | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index bccef30..1003ee1 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -314,17 +314,21 @@ static int temac_dma_bd_init(struct net_device *ndev)
CHNL_CTRL_IRQ_EN | CHNL_CTRL_IRQ_ERR_EN |
CHNL_CTRL_IRQ_DLY_EN | CHNL_CTRL_IRQ_COAL_EN);

- lp->dma_out(lp, RX_CURDESC_PTR, lp->rx_bd_p);
- lp->dma_out(lp, RX_TAILDESC_PTR,
- lp->rx_bd_p + (sizeof(*lp->rx_bd_v) * (RX_BD_NUM - 1)));
- lp->dma_out(lp, TX_CURDESC_PTR, lp->tx_bd_p);
-
/* Init descriptor indexes */
lp->tx_bd_ci = 0;
lp->tx_bd_next = 0;
lp->tx_bd_tail = 0;
lp->rx_bd_ci = 0;

+ /* Enable RX DMA transfers */
+ wmb();
+ lp->dma_out(lp, RX_CURDESC_PTR, lp->rx_bd_p);
+ lp->dma_out(lp, RX_TAILDESC_PTR,
+ lp->rx_bd_p + (sizeof(*lp->rx_bd_v) * (RX_BD_NUM - 1)));
+
+ /* Prepare for TX DMA transfer */
+ lp->dma_out(lp, TX_CURDESC_PTR, lp->tx_bd_p);
+
return 0;

out:
@@ -789,6 +793,7 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
skb_tx_timestamp(skb);

/* Kick off the transfer */
+ wmb();
lp->dma_out(lp, TX_TAILDESC_PTR, tail_p); /* DMA start */

return NETDEV_TX_OK;
--
2.4.11

2019-04-30 07:20:40

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH v3 10/12] net: ll_temac: Replace bad usage of msleep() with usleep_range()

Use usleep_range() to avoid problems with msleep() actually sleeping
much longer than expected.

Signed-off-by: Esben Haabendal <[email protected]>
Reviewed-by: Andrew Lunn <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac_main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 6837565..fec8e4c 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -92,7 +92,7 @@ int temac_indirect_busywait(struct temac_local *lp)
WARN_ON(1);
return -ETIMEDOUT;
}
- msleep(1);
+ usleep_range(500, 1000);
}
return 0;
}
--
2.4.11

2019-04-30 07:21:06

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH v3 06/12] net: ll_temac: Allow use on x86 platforms

With little-endian and 64-bit support in place, the ll_temac driver can
now be used on x86 and x86_64 platforms.

And while at it, enable COMPILE_TEST also.

Signed-off-by: Esben Haabendal <[email protected]>
Reviewed-by: Andrew Lunn <[email protected]>
---
drivers/net/ethernet/xilinx/Kconfig | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig
index 6d68c8a..db448fa 100644
--- a/drivers/net/ethernet/xilinx/Kconfig
+++ b/drivers/net/ethernet/xilinx/Kconfig
@@ -5,7 +5,7 @@
config NET_VENDOR_XILINX
bool "Xilinx devices"
default y
- depends on PPC || PPC32 || MICROBLAZE || ARCH_ZYNQ || MIPS
+ depends on PPC || PPC32 || MICROBLAZE || ARCH_ZYNQ || MIPS || X86 || COMPILE_TEST
---help---
If you have a network (Ethernet) card belonging to this class, say Y.

@@ -33,7 +33,7 @@ config XILINX_AXI_EMAC

config XILINX_LL_TEMAC
tristate "Xilinx LL TEMAC (LocalLink Tri-mode Ethernet MAC) driver"
- depends on (PPC || MICROBLAZE)
+ depends on PPC || MICROBLAZE || X86 || COMPILE_TEST
select PHYLIB
---help---
This driver supports the Xilinx 10/100/1000 LocalLink TEMAC
--
2.4.11

2019-04-30 07:21:14

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH v3 02/12] net: ll_temac: Extend support to non-device-tree platforms

Support initialization with platdata, so the driver can be used on
non-device-tree platforms.

For currently supported device-tree platforms, the driver should behave
as before.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac.h | 3 +
drivers/net/ethernet/xilinx/ll_temac_main.c | 187 +++++++++++++++++---------
drivers/net/ethernet/xilinx/ll_temac_mdio.c | 23 +++-
include/linux/platform_data/xilinx-ll-temac.h | 19 +++
4 files changed, 166 insertions(+), 66 deletions(-)
create mode 100644 include/linux/platform_data/xilinx-ll-temac.h

diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h
index 4557578..e338b4f 100644
--- a/drivers/net/ethernet/xilinx/ll_temac.h
+++ b/drivers/net/ethernet/xilinx/ll_temac.h
@@ -334,6 +334,9 @@ struct temac_local {

/* Connection to PHY device */
struct device_node *phy_node;
+ /* For non-device-tree devices */
+ char phy_name[MII_BUS_ID_SIZE + 3];
+ phy_interface_t phy_interface;

/* MDIO bus data */
struct mii_bus *mii_bus; /* MII bus reference */
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index c4e85a9..fddd1b3 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -33,6 +33,7 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/netdevice.h>
+#include <linux/if_ether.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
@@ -51,6 +52,7 @@
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
+#include <linux/platform_data/xilinx-ll-temac.h>

#include "ll_temac.h"

@@ -187,7 +189,7 @@ static int temac_dcr_setup(struct temac_local *lp, struct platform_device *op,

/*
* temac_dcr_setup - This is a stub for when DCR is not supported,
- * such as with MicroBlaze
+ * such as with MicroBlaze and x86
*/
static int temac_dcr_setup(struct temac_local *lp, struct platform_device *op,
struct device_node *np)
@@ -857,7 +859,14 @@ static int temac_open(struct net_device *ndev)
dev_err(lp->dev, "of_phy_connect() failed\n");
return -ENODEV;
}
-
+ phy_start(phydev);
+ } else if (strlen(lp->phy_name) > 0) {
+ phydev = phy_connect(lp->ndev, lp->phy_name, temac_adjust_link,
+ lp->phy_interface);
+ if (!phydev) {
+ dev_err(lp->dev, "phy_connect() failed\n");
+ return -ENODEV;
+ }
phy_start(phydev);
}

@@ -977,11 +986,13 @@ static const struct ethtool_ops temac_ethtool_ops = {
.set_link_ksettings = phy_ethtool_set_link_ksettings,
};

-static int temac_of_probe(struct platform_device *op)
+static int temac_probe(struct platform_device *pdev)
{
- struct device_node *np;
+ struct ll_temac_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct device_node *temac_np = dev_of_node(&pdev->dev), *dma_np;
struct temac_local *lp;
struct net_device *ndev;
+ struct resource *res;
const void *addr;
__be32 *p;
int rc = 0;
@@ -991,8 +1002,8 @@ static int temac_of_probe(struct platform_device *op)
if (!ndev)
return -ENOMEM;

- platform_set_drvdata(op, ndev);
- SET_NETDEV_DEV(ndev, &op->dev);
+ platform_set_drvdata(pdev, ndev);
+ SET_NETDEV_DEV(ndev, &pdev->dev);
ndev->flags &= ~IFF_MULTICAST; /* clear multicast */
ndev->features = NETIF_F_SG;
ndev->netdev_ops = &temac_netdev_ops;
@@ -1014,79 +1025,129 @@ static int temac_of_probe(struct platform_device *op)
/* setup temac private info structure */
lp = netdev_priv(ndev);
lp->ndev = ndev;
- lp->dev = &op->dev;
+ lp->dev = &pdev->dev;
lp->options = XTE_OPTION_DEFAULTS;
spin_lock_init(&lp->rx_lock);
mutex_init(&lp->indirect_mutex);

/* map device registers */
- lp->regs = devm_of_iomap(&op->dev, op->dev.of_node, 0, NULL);
- if (!lp->regs) {
- dev_err(&op->dev, "could not map temac regs.\n");
- return -ENOMEM;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ lp->regs = devm_ioremap_nocache(&pdev->dev, res->start,
+ resource_size(res));
+ if (IS_ERR(lp->regs)) {
+ dev_err(&pdev->dev, "could not map TEMAC registers\n");
+ return PTR_ERR(lp->regs);
}

/* Setup checksum offload, but default to off if not specified */
lp->temac_features = 0;
- p = (__be32 *)of_get_property(op->dev.of_node, "xlnx,txcsum", NULL);
- if (p && be32_to_cpu(*p)) {
- lp->temac_features |= TEMAC_FEATURE_TX_CSUM;
+ if (temac_np) {
+ p = (__be32 *)of_get_property(temac_np, "xlnx,txcsum", NULL);
+ if (p && be32_to_cpu(*p))
+ lp->temac_features |= TEMAC_FEATURE_TX_CSUM;
+ p = (__be32 *)of_get_property(temac_np, "xlnx,rxcsum", NULL);
+ if (p && be32_to_cpu(*p))
+ lp->temac_features |= TEMAC_FEATURE_RX_CSUM;
+ } else if (pdata) {
+ if (pdata->txcsum)
+ lp->temac_features |= TEMAC_FEATURE_TX_CSUM;
+ if (pdata->rxcsum)
+ lp->temac_features |= TEMAC_FEATURE_RX_CSUM;
+ }
+ if (lp->temac_features & TEMAC_FEATURE_TX_CSUM)
/* Can checksum TCP/UDP over IPv4. */
ndev->features |= NETIF_F_IP_CSUM;
- }
- p = (__be32 *)of_get_property(op->dev.of_node, "xlnx,rxcsum", NULL);
- if (p && be32_to_cpu(*p))
- lp->temac_features |= TEMAC_FEATURE_RX_CSUM;
-
- /* Find the DMA node, map the DMA registers, and decode the DMA IRQs */
- np = of_parse_phandle(op->dev.of_node, "llink-connected", 0);
- if (!np) {
- dev_err(&op->dev, "could not find DMA node\n");
- return -ENODEV;
- }

- /* Setup the DMA register accesses, could be DCR or memory mapped */
- if (temac_dcr_setup(lp, op, np)) {
+ /* Setup LocalLink DMA */
+ if (temac_np) {
+ /* Find the DMA node, map the DMA registers, and
+ * decode the DMA IRQs.
+ */
+ dma_np = of_parse_phandle(temac_np, "llink-connected", 0);
+ if (!dma_np) {
+ dev_err(&pdev->dev, "could not find DMA node\n");
+ return -ENODEV;
+ }

- /* no DCR in the device tree, try non-DCR */
- lp->sdma_regs = devm_of_iomap(&op->dev, np, 0, NULL);
- if (lp->sdma_regs) {
+ /* Setup the DMA register accesses, could be DCR or
+ * memory mapped.
+ */
+ if (temac_dcr_setup(lp, pdev, dma_np)) {
+ /* no DCR in the device tree, try non-DCR */
+ lp->sdma_regs = devm_of_iomap(&pdev->dev, dma_np, 0,
+ NULL);
+ if (IS_ERR(lp->sdma_regs)) {
+ dev_err(&pdev->dev,
+ "unable to map DMA registers\n");
+ of_node_put(dma_np);
+ return PTR_ERR(lp->sdma_regs);
+ }
lp->dma_in = temac_dma_in32;
lp->dma_out = temac_dma_out32;
- dev_dbg(&op->dev, "MEM base: %p\n", lp->sdma_regs);
- } else {
- dev_err(&op->dev, "unable to map DMA registers\n");
- of_node_put(np);
- return -ENOMEM;
+ dev_dbg(&pdev->dev, "MEM base: %p\n", lp->sdma_regs);
}
- }
-
- lp->rx_irq = irq_of_parse_and_map(np, 0);
- lp->tx_irq = irq_of_parse_and_map(np, 1);

- of_node_put(np); /* Finished with the DMA node; drop the reference */
+ /* Get DMA RX and TX interrupts */
+ lp->rx_irq = irq_of_parse_and_map(dma_np, 0);
+ lp->tx_irq = irq_of_parse_and_map(dma_np, 1);
+
+ /* Finished with the DMA node; drop the reference */
+ of_node_put(dma_np);
+ } else if (pdata) {
+ /* 2nd memory resource specifies DMA registers */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ lp->sdma_regs = devm_ioremap_nocache(&pdev->dev, res->start,
+ resource_size(res));
+ if (IS_ERR(lp->sdma_regs)) {
+ dev_err(&pdev->dev,
+ "could not map DMA registers\n");
+ return PTR_ERR(lp->sdma_regs);
+ }
+ lp->dma_in = temac_dma_in32;
+ lp->dma_out = temac_dma_out32;

- if (!lp->rx_irq || !lp->tx_irq) {
- dev_err(&op->dev, "could not determine irqs\n");
- return -ENOMEM;
+ /* Get DMA RX and TX interrupts */
+ lp->rx_irq = platform_get_irq(pdev, 0);
+ lp->tx_irq = platform_get_irq(pdev, 1);
}

+ /* Error handle returned DMA RX and TX interrupts */
+ if (lp->rx_irq < 0) {
+ if (lp->rx_irq != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "could not get DMA RX irq\n");
+ return lp->rx_irq;
+ }
+ if (lp->tx_irq < 0) {
+ if (lp->tx_irq != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "could not get DMA TX irq\n");
+ return lp->tx_irq;
+ }

- /* Retrieve the MAC address */
- addr = of_get_mac_address(op->dev.of_node);
- if (!addr) {
- dev_err(&op->dev, "could not find MAC address\n");
- return -ENODEV;
+ if (temac_np) {
+ /* Retrieve the MAC address */
+ addr = of_get_mac_address(temac_np);
+ if (!addr) {
+ dev_err(&pdev->dev, "could not find MAC address\n");
+ return -ENODEV;
+ }
+ temac_init_mac_address(ndev, addr);
+ } else if (pdata) {
+ temac_init_mac_address(ndev, pdata->mac_addr);
}
- temac_init_mac_address(ndev, addr);

rc = temac_mdio_setup(lp, pdev);
if (rc)
- dev_warn(&op->dev, "error registering MDIO bus\n");
-
- lp->phy_node = of_parse_phandle(op->dev.of_node, "phy-handle", 0);
- if (lp->phy_node)
- dev_dbg(lp->dev, "using PHY node %pOF (%p)\n", np, np);
+ dev_warn(&pdev->dev, "error registering MDIO bus\n");
+
+ if (temac_np) {
+ lp->phy_node = of_parse_phandle(temac_np, "phy-handle", 0);
+ if (lp->phy_node)
+ dev_dbg(lp->dev, "using PHY node %pOF\n", temac_np);
+ } else if (pdata) {
+ snprintf(lp->phy_name, sizeof(lp->phy_name),
+ PHY_ID_FMT, lp->mii_bus->id, pdata->phy_addr);
+ lp->phy_interface = pdata->phy_interface;
+ }

/* Add the device attributes */
rc = sysfs_create_group(&lp->dev->kobj, &temac_attr_group);
@@ -1106,19 +1167,21 @@ static int temac_of_probe(struct platform_device *op)
err_register_ndev:
sysfs_remove_group(&lp->dev->kobj, &temac_attr_group);
err_sysfs_create:
- of_node_put(lp->phy_node);
+ if (lp->phy_node)
+ of_node_put(lp->phy_node);
temac_mdio_teardown(lp);
return rc;
}

-static int temac_of_remove(struct platform_device *op)
+static int temac_remove(struct platform_device *pdev)
{
- struct net_device *ndev = platform_get_drvdata(op);
+ struct net_device *ndev = platform_get_drvdata(pdev);
struct temac_local *lp = netdev_priv(ndev);

unregister_netdev(ndev);
sysfs_remove_group(&lp->dev->kobj, &temac_attr_group);
- of_node_put(lp->phy_node);
+ if (lp->phy_node)
+ of_node_put(lp->phy_node);
temac_mdio_teardown(lp);
return 0;
}
@@ -1132,16 +1195,16 @@ static const struct of_device_id temac_of_match[] = {
};
MODULE_DEVICE_TABLE(of, temac_of_match);

-static struct platform_driver temac_of_driver = {
- .probe = temac_of_probe,
- .remove = temac_of_remove,
+static struct platform_driver temac_driver = {
+ .probe = temac_probe,
+ .remove = temac_remove,
.driver = {
.name = "xilinx_temac",
.of_match_table = temac_of_match,
},
};

-module_platform_driver(temac_of_driver);
+module_platform_driver(temac_driver);

MODULE_DESCRIPTION("Xilinx LL_TEMAC Ethernet driver");
MODULE_AUTHOR("Yoshio Kashiwagi");
diff --git a/drivers/net/ethernet/xilinx/ll_temac_mdio.c b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
index a0b365e..c5307e5 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_mdio.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
@@ -14,6 +14,7 @@
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/of_mdio.h>
+#include <linux/platform_data/xilinx-ll-temac.h>

#include "ll_temac.h"

@@ -59,6 +60,7 @@ static int temac_mdio_write(struct mii_bus *bus, int phy_id, int reg, u16 val)

int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)
{
+ struct ll_temac_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct device_node *np = dev_of_node(&pdev->dev);
struct mii_bus *bus;
u32 bus_hz;
@@ -66,9 +68,16 @@ int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)
int rc;
struct resource res;

+ /* Get MDIO bus frequency (if specified) */
+ bus_hz = 0;
+ if (np)
+ of_property_read_u32(np, "clock-frequency", &bus_hz);
+ else if (pdata)
+ bus_hz = pdata->mdio_clk_freq;
+
/* Calculate a reasonable divisor for the clock rate */
clk_div = 0x3f; /* worst-case default setting */
- if (of_property_read_u32(np, "clock-frequency", &bus_hz) == 0) {
+ if (bus_hz != 0) {
clk_div = bus_hz / (2500 * 1000 * 2) - 1;
if (clk_div < 1)
clk_div = 1;
@@ -86,9 +95,15 @@ int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)
if (!bus)
return -ENOMEM;

- of_address_to_resource(np, 0, &res);
- snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx",
- (unsigned long long)res.start);
+ if (np) {
+ of_address_to_resource(np, 0, &res);
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx",
+ (unsigned long long)res.start);
+ } else if (pdata && pdata->mdio_bus_id >= 0) {
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx",
+ pdata->mdio_bus_id);
+ }
+
bus->priv = lp;
bus->name = "Xilinx TEMAC MDIO";
bus->read = temac_mdio_read;
diff --git a/include/linux/platform_data/xilinx-ll-temac.h b/include/linux/platform_data/xilinx-ll-temac.h
new file mode 100644
index 0000000..82e2f80
--- /dev/null
+++ b/include/linux/platform_data/xilinx-ll-temac.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __LINUX_XILINX_LL_TEMAC_H
+#define __LINUX_XILINX_LL_TEMAC_H
+
+#include <linux/if_ether.h>
+#include <linux/phy.h>
+
+struct ll_temac_platform_data {
+ bool txcsum; /* Enable/disable TX checksum */
+ bool rxcsum; /* Enable/disable RX checksum */
+ u8 mac_addr[ETH_ALEN]; /* MAC address (6 bytes) */
+ /* Clock frequency for input to MDIO clock generator */
+ u32 mdio_clk_freq;
+ unsigned long long mdio_bus_id; /* Unique id for MDIO bus */
+ int phy_addr; /* Address of the PHY to connect to */
+ phy_interface_t phy_interface; /* PHY interface mode */
+};
+
+#endif /* __LINUX_XILINX_LL_TEMAC_H */
--
2.4.11

2019-04-30 07:21:41

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH v3 11/12] net: ll_temac: Allow configuration of IRQ coalescing

This allows custom setup of IRQ coalescing for platforms using legacy
platform_device. The irq timeout and count parameters can be used for
tuning cpu load vs. latency.

I have maintained the 0x00000400 bit in TX_CHNL_CTRL. It is specified as
unused in the documentation I have available. It does not make any
difference in the hardware I have available, so it is left in to not risk
breaking other platforms where it might be used.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac.h | 4 +++
drivers/net/ethernet/xilinx/ll_temac_main.c | 40 +++++++++++++++++++--------
include/linux/platform_data/xilinx-ll-temac.h | 5 ++++
3 files changed, 37 insertions(+), 12 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h
index 990f9ed..1aeda08 100644
--- a/drivers/net/ethernet/xilinx/ll_temac.h
+++ b/drivers/net/ethernet/xilinx/ll_temac.h
@@ -375,6 +375,10 @@ struct temac_local {
int tx_bd_next;
int tx_bd_tail;
int rx_bd_ci;
+
+ /* DMA channel control setup */
+ u32 tx_chnl_ctrl;
+ u32 rx_chnl_ctrl;
};

/* Wrappers for temac_ior()/temac_iow() function pointers above */
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index fec8e4c..bccef30 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -304,18 +304,15 @@ static int temac_dma_bd_init(struct net_device *ndev)
lp->rx_bd_v[i].app0 = cpu_to_be32(STS_CTRL_APP0_IRQONEND);
}

- lp->dma_out(lp, TX_CHNL_CTRL, 0x10220400 |
- CHNL_CTRL_IRQ_EN |
- CHNL_CTRL_IRQ_DLY_EN |
- CHNL_CTRL_IRQ_COAL_EN);
- /* 0x10220483 */
- /* 0x00100483 */
- lp->dma_out(lp, RX_CHNL_CTRL, 0xff070000 |
- CHNL_CTRL_IRQ_EN |
- CHNL_CTRL_IRQ_DLY_EN |
- CHNL_CTRL_IRQ_COAL_EN |
- CHNL_CTRL_IRQ_IOE);
- /* 0xff010283 */
+ /* Configure DMA channel (irq setup) */
+ lp->dma_out(lp, TX_CHNL_CTRL, lp->tx_chnl_ctrl |
+ 0x00000400 | // Use 1 Bit Wide Counters. Currently Not Used!
+ CHNL_CTRL_IRQ_EN | CHNL_CTRL_IRQ_ERR_EN |
+ CHNL_CTRL_IRQ_DLY_EN | CHNL_CTRL_IRQ_COAL_EN);
+ lp->dma_out(lp, RX_CHNL_CTRL, lp->rx_chnl_ctrl |
+ CHNL_CTRL_IRQ_IOE |
+ CHNL_CTRL_IRQ_EN | CHNL_CTRL_IRQ_ERR_EN |
+ CHNL_CTRL_IRQ_DLY_EN | CHNL_CTRL_IRQ_COAL_EN);

lp->dma_out(lp, RX_CURDESC_PTR, lp->rx_bd_p);
lp->dma_out(lp, RX_TAILDESC_PTR,
@@ -1191,6 +1188,13 @@ static int temac_probe(struct platform_device *pdev)
lp->rx_irq = irq_of_parse_and_map(dma_np, 0);
lp->tx_irq = irq_of_parse_and_map(dma_np, 1);

+ /* Use defaults for IRQ delay/coalescing setup. These
+ * are configuration values, so does not belong in
+ * device-tree.
+ */
+ lp->tx_chnl_ctrl = 0x10220000;
+ lp->rx_chnl_ctrl = 0xff070000;
+
/* Finished with the DMA node; drop the reference */
of_node_put(dma_np);
} else if (pdata) {
@@ -1214,6 +1218,18 @@ static int temac_probe(struct platform_device *pdev)
/* Get DMA RX and TX interrupts */
lp->rx_irq = platform_get_irq(pdev, 0);
lp->tx_irq = platform_get_irq(pdev, 1);
+
+ /* IRQ delay/coalescing setup */
+ if (pdata->tx_irq_timeout || pdata->tx_irq_count)
+ lp->tx_chnl_ctrl = (pdata->tx_irq_timeout << 24) |
+ (pdata->tx_irq_count << 16);
+ else
+ lp->tx_chnl_ctrl = 0x10220000;
+ if (pdata->rx_irq_timeout || pdata->rx_irq_count)
+ lp->rx_chnl_ctrl = (pdata->rx_irq_timeout << 24) |
+ (pdata->rx_irq_count << 16);
+ else
+ lp->rx_chnl_ctrl = 0xff070000;
}

/* Error handle returned DMA RX and TX interrupts */
diff --git a/include/linux/platform_data/xilinx-ll-temac.h b/include/linux/platform_data/xilinx-ll-temac.h
index b0b8238..368530f 100644
--- a/include/linux/platform_data/xilinx-ll-temac.h
+++ b/include/linux/platform_data/xilinx-ll-temac.h
@@ -22,6 +22,11 @@ struct ll_temac_platform_data {
* they share the same DCR bus bridge.
*/
struct mutex *indirect_mutex;
+ /* DMA channel control setup */
+ u8 tx_irq_timeout; /* TX Interrupt Delay Time-out */
+ u8 tx_irq_count; /* TX Interrupt Coalescing Threshold Count */
+ u8 rx_irq_timeout; /* RX Interrupt Delay Time-out */
+ u8 rx_irq_count; /* RX Interrupt Coalescing Threshold Count */
};

#endif /* __LINUX_XILINX_LL_TEMAC_H */
--
2.4.11

2019-04-30 07:21:48

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH v3 07/12] net: ll_temac: Support indirect_mutex share within TEMAC IP

Indirect register access goes through a DCR bus bridge, which
allows only one outstanding transaction. And to make matters
worse, each TEMAC IP block contains two Ethernet interfaces, and
although they seem to have separate registers for indirect access,
they actually share the registers. Or to be more specific, MSW, LSW
and CTL registers are physically shared between Ethernet interfaces
in same TEMAC IP, with RDY register being (almost) specificic to
the Ethernet interface. The 0x10000 bit in RDY reflects combined
bus ready state though.

So we need to take care to synchronize not only within a single
device, but also between devices in same TEMAC IP.

This commit allows to do that with legacy platform devices.

For OF devices, the xlnx,compound parent of the temac node should be
used to find siblings, and setup a shared indirect_mutex between them.
I will leave this work to somebody else, as I don't have hardware to
test that. No regression is introduced by that, as before this commit
using two Ethernet interfaces in same TEMAC block is simply broken.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac.h | 5 +++-
drivers/net/ethernet/xilinx/ll_temac_main.c | 36 +++++++++++++++++++--------
drivers/net/ethernet/xilinx/ll_temac_mdio.c | 16 ++++++------
include/linux/platform_data/xilinx-ll-temac.h | 6 +++++
4 files changed, 43 insertions(+), 20 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac.h b/drivers/net/ethernet/xilinx/ll_temac.h
index 23d8dd5..990f9ed 100644
--- a/drivers/net/ethernet/xilinx/ll_temac.h
+++ b/drivers/net/ethernet/xilinx/ll_temac.h
@@ -358,7 +358,10 @@ struct temac_local {

struct sk_buff **rx_skb;
spinlock_t rx_lock;
- struct mutex indirect_mutex;
+ /* For synchronization of indirect register access. Must be
+ * shared mutex between interfaces in same TEMAC block.
+ */
+ struct mutex *indirect_mutex;
u32 options; /* Current options word */
int last_link;
unsigned int temac_features;
diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 179a998..d3899e7 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -344,7 +344,7 @@ static void temac_do_set_mac_address(struct net_device *ndev)
struct temac_local *lp = netdev_priv(ndev);

/* set up unicast MAC address filter set its mac address */
- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
temac_indirect_out32(lp, XTE_UAW0_OFFSET,
(ndev->dev_addr[0]) |
(ndev->dev_addr[1] << 8) |
@@ -355,7 +355,7 @@ static void temac_do_set_mac_address(struct net_device *ndev)
temac_indirect_out32(lp, XTE_UAW1_OFFSET,
(ndev->dev_addr[4] & 0x000000ff) |
(ndev->dev_addr[5] << 8));
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);
}

static int temac_init_mac_address(struct net_device *ndev, const void *address)
@@ -384,7 +384,7 @@ static void temac_set_multicast_list(struct net_device *ndev)
u32 multi_addr_msw, multi_addr_lsw, val;
int i;

- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
if (ndev->flags & (IFF_ALLMULTI | IFF_PROMISC) ||
netdev_mc_count(ndev) > MULTICAST_CAM_TABLE_NUM) {
/*
@@ -423,7 +423,7 @@ static void temac_set_multicast_list(struct net_device *ndev)
temac_indirect_out32(lp, XTE_MAW1_OFFSET, 0);
dev_info(&ndev->dev, "Promiscuous mode disabled.\n");
}
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);
}

static struct temac_option {
@@ -515,7 +515,7 @@ static u32 temac_setoptions(struct net_device *ndev, u32 options)
struct temac_option *tp = &temac_options[0];
int reg;

- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
while (tp->opt) {
reg = temac_indirect_in32(lp, tp->reg) & ~tp->m_or;
if (options & tp->opt)
@@ -524,7 +524,7 @@ static u32 temac_setoptions(struct net_device *ndev, u32 options)
tp++;
}
lp->options |= options;
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);

return 0;
}
@@ -543,7 +543,7 @@ static void temac_device_reset(struct net_device *ndev)

dev_dbg(&ndev->dev, "%s()\n", __func__);

- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
/* Reset the receiver and wait for it to finish reset */
temac_indirect_out32(lp, XTE_RXC1_OFFSET, XTE_RXC1_RXRST_MASK);
timeout = 1000;
@@ -595,7 +595,7 @@ static void temac_device_reset(struct net_device *ndev)
temac_indirect_out32(lp, XTE_TXC_OFFSET, 0);
temac_indirect_out32(lp, XTE_FCC_OFFSET, XTE_FCC_RXFLO_MASK);

- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);

/* Sync default options with HW
* but leave receiver and transmitter disabled. */
@@ -623,7 +623,7 @@ static void temac_adjust_link(struct net_device *ndev)
/* hash together the state values to decide if something has changed */
link_state = phy->speed | (phy->duplex << 1) | phy->link;

- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
if (lp->last_link != link_state) {
mii_speed = temac_indirect_in32(lp, XTE_EMCFG_OFFSET);
mii_speed &= ~XTE_EMCFG_LINKSPD_MASK;
@@ -639,7 +639,7 @@ static void temac_adjust_link(struct net_device *ndev)
lp->last_link = link_state;
phy_print_status(phy);
}
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);
}

#ifdef CONFIG_64BIT
@@ -1091,7 +1091,21 @@ static int temac_probe(struct platform_device *pdev)
lp->dev = &pdev->dev;
lp->options = XTE_OPTION_DEFAULTS;
spin_lock_init(&lp->rx_lock);
- mutex_init(&lp->indirect_mutex);
+
+ /* Setup mutex for synchronization of indirect register access */
+ if (pdata) {
+ if (!pdata->indirect_mutex) {
+ dev_err(&pdev->dev,
+ "indirect_mutex missing in platform_data\n");
+ return -EINVAL;
+ }
+ lp->indirect_mutex = pdata->indirect_mutex;
+ } else {
+ lp->indirect_mutex = devm_kmalloc(&pdev->dev,
+ sizeof(*lp->indirect_mutex),
+ GFP_KERNEL);
+ mutex_init(lp->indirect_mutex);
+ }

/* map device registers */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
diff --git a/drivers/net/ethernet/xilinx/ll_temac_mdio.c b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
index c5307e5..c2a1170 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_mdio.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_mdio.c
@@ -29,10 +29,10 @@ static int temac_mdio_read(struct mii_bus *bus, int phy_id, int reg)
/* Write the PHY address to the MIIM Access Initiator register.
* When the transfer completes, the PHY register value will appear
* in the LSW0 register */
- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
temac_iow(lp, XTE_LSW0_OFFSET, (phy_id << 5) | reg);
rc = temac_indirect_in32(lp, XTE_MIIMAI_OFFSET);
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);

dev_dbg(lp->dev, "temac_mdio_read(phy_id=%i, reg=%x) == %x\n",
phy_id, reg, rc);
@@ -50,10 +50,10 @@ static int temac_mdio_write(struct mii_bus *bus, int phy_id, int reg, u16 val)
/* First write the desired value into the write data register
* and then write the address into the access initiator register
*/
- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
temac_indirect_out32(lp, XTE_MGTDR_OFFSET, val);
temac_indirect_out32(lp, XTE_MIIMAI_OFFSET, (phy_id << 5) | reg);
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);

return 0;
}
@@ -87,9 +87,9 @@ int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)

/* Enable the MDIO bus by asserting the enable bit and writing
* in the clock config */
- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
temac_indirect_out32(lp, XTE_MC_OFFSET, 1 << 6 | clk_div);
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);

bus = devm_mdiobus_alloc(&pdev->dev);
if (!bus)
@@ -116,10 +116,10 @@ int temac_mdio_setup(struct temac_local *lp, struct platform_device *pdev)
if (rc)
return rc;

- mutex_lock(&lp->indirect_mutex);
+ mutex_lock(lp->indirect_mutex);
dev_dbg(lp->dev, "MDIO bus registered; MC:%x\n",
temac_indirect_in32(lp, XTE_MC_OFFSET));
- mutex_unlock(&lp->indirect_mutex);
+ mutex_unlock(lp->indirect_mutex);
return 0;
}

diff --git a/include/linux/platform_data/xilinx-ll-temac.h b/include/linux/platform_data/xilinx-ll-temac.h
index af87927..b0b8238 100644
--- a/include/linux/platform_data/xilinx-ll-temac.h
+++ b/include/linux/platform_data/xilinx-ll-temac.h
@@ -16,6 +16,12 @@ struct ll_temac_platform_data {
phy_interface_t phy_interface; /* PHY interface mode */
bool reg_little_endian; /* Little endian TEMAC register access */
bool dma_little_endian; /* Little endian DMA register access */
+ /* Pre-initialized mutex to use for synchronizing indirect
+ * register access. When using both interfaces of a single
+ * TEMAC IP block, the same mutex should be passed here, as
+ * they share the same DCR bus bridge.
+ */
+ struct mutex *indirect_mutex;
};

#endif /* __LINUX_XILINX_LL_TEMAC_H */
--
2.4.11

2019-04-30 07:22:02

by Esben Haabendal

[permalink] [raw]
Subject: [PATCH v3 09/12] net: ll_temac: Fix bug causing buffer descriptor overrun

As we are actually using a BD for both the skb and each frag contained in
it, the oldest TX BD would be overwritten when there was exactly one BD
less than needed.

Signed-off-by: Esben Haabendal <[email protected]>
---
drivers/net/ethernet/xilinx/ll_temac_main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/xilinx/ll_temac_main.c b/drivers/net/ethernet/xilinx/ll_temac_main.c
index 7e42746..6837565 100644
--- a/drivers/net/ethernet/xilinx/ll_temac_main.c
+++ b/drivers/net/ethernet/xilinx/ll_temac_main.c
@@ -743,7 +743,7 @@ temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
start_p = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * lp->tx_bd_tail;
cur_p = &lp->tx_bd_v[lp->tx_bd_tail];

- if (temac_check_tx_bd_space(lp, num_frag)) {
+ if (temac_check_tx_bd_space(lp, num_frag + 1)) {
if (!netif_queue_stopped(ndev))
netif_stop_queue(ndev);
return NETDEV_TX_BUSY;
--
2.4.11

2019-04-30 17:01:22

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH v3 07/12] net: ll_temac: Support indirect_mutex share within TEMAC IP

On Tue, Apr 30, 2019 at 09:17:54AM +0200, Esben Haabendal wrote:
> Indirect register access goes through a DCR bus bridge, which
> allows only one outstanding transaction. And to make matters
> worse, each TEMAC IP block contains two Ethernet interfaces, and
> although they seem to have separate registers for indirect access,
> they actually share the registers. Or to be more specific, MSW, LSW
> and CTL registers are physically shared between Ethernet interfaces
> in same TEMAC IP, with RDY register being (almost) specificic to
> the Ethernet interface. The 0x10000 bit in RDY reflects combined
> bus ready state though.
>
> So we need to take care to synchronize not only within a single
> device, but also between devices in same TEMAC IP.
>
> This commit allows to do that with legacy platform devices.
>
> For OF devices, the xlnx,compound parent of the temac node should be
> used to find siblings, and setup a shared indirect_mutex between them.
> I will leave this work to somebody else, as I don't have hardware to
> test that. No regression is introduced by that, as before this commit
> using two Ethernet interfaces in same TEMAC block is simply broken.
>
> Signed-off-by: Esben Haabendal <[email protected]>

Reviewed-by: Andrew Lunn <[email protected]>

Andrew

2019-04-30 17:02:24

by Andrew Lunn

[permalink] [raw]
Subject: Re: [PATCH v3 08/12] net: ll_temac: Fix iommu/swiotlb leak

On Tue, Apr 30, 2019 at 09:17:55AM +0200, Esben Haabendal wrote:
> Unmap the actual buffer length, not the amount of data received, avoiding
> resource exhaustion of swiotlb (seen on x86_64 platform).
>
> Signed-off-by: Esben Haabendal <[email protected]>

Reviewed-by: Andrew Lunn <[email protected]>

Andrew

2019-05-01 18:35:11

by David Miller

[permalink] [raw]
Subject: Re: [PATCH v3 00/12] net: ll_temac: x86_64 support

From: Esben Haabendal <[email protected]>
Date: Tue, 30 Apr 2019 09:17:47 +0200

> This patch series adds support for use of ll_temac driver with
> platform_data configuration and fixes endianess and 64-bit problems so
> that it can be used on x86_64 platform.
>
> A few bugfixes are also included.

Series applied to net-next.