[PATCH net-next 2/2 v6] net: ethernet: Add a driver for Gemini gigabit ethernet
Michał Mirosław
mirq-linux at rere.qmqm.pl
Tue Dec 5 11:03:31 PST 2017
On Sat, Dec 02, 2017 at 12:06:40PM +0100, Linus Walleij wrote:
[...]
> The latest v6 incarnation of this driver was written by Michał
> Mirosław and submitted for inclusion in 2011. This was the
> last post:
> https://lwn.net/Articles/437889/
>
> DaveM ACKed it at the time:
> https://marc.info/?l=linux-netdev&m=130255434310315&w=2
>
> The controversial pieces under ARM (board files) and other
> subsystems are now gone and replaced by DeviceTree.
>
> Michał: I hope you don't mind me picking it up and hope
> you can still test it on your ICYbox, I have device tree
> patches in my tree:
> https://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-nomadik.git/log/?h=gemini-ethernet
I'm happy to see that my work didn't go to /dev/null after all.
I haven't finished it at the time because the box I had broke down
beyond repair.
I skimmed through the patch - please find my comments below.
Best Regards,
Michał Mirosław
[...]
> --- /dev/null
> +++ b/drivers/net/ethernet/cortina/gemini.c
> @@ -0,0 +1,2461 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Ethernet device driver for Cortina Systems Gemini SoC
> + * Also known as the StorLink SL3512 and SL3516 (SL351x) GMAC
> + *
> + * Authors:
> + * Linus Walleij <linus.walleij at linaro.org>
> + * Tobias Waldvogel <tobias.waldvogel at gmail.com> (OpenWRT)
> + * MichaÅ MirosÅaw <mirq-linux at rere.qmqm.pl>
Doubly UTF-8 encoded?
[...]
> +static int gmac_setup_txqs(struct net_device *netdev)
> +{
[...]
> + desc_ring = dma_alloc_coherent(geth->dev, len * sizeof(*desc_ring),
> + &port->txq_dma_base, GFP_KERNEL);
> +
> + if (!desc_ring) {
Should check with dma_mapping_error().
> + kfree(skb_tab);
> + return -ENOMEM;
> + }
> +
> + BUG_ON(port->txq_dma_base & ~DMA_Q_BASE_MASK);
BUG is too hard here. return -EINVAL or other error? Shouldn't happen
if dma_alloc_coherent() guarantees 16B alignment.
[...]
> +static int gmac_setup_rxq(struct net_device *netdev)
> +{
[...]
> + BUG_ON(port->rxq_dma_base & ~NONTOE_QHDR0_BASE_MASK);
Like above.
[...]
> +static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
> + int pn)
> +{
> + unsigned int fpp_order = PAGE_SHIFT - geth->freeq_frag_order;
> + unsigned int frag_len = 1 << geth->freeq_frag_order;
> + GMAC_RXDESC_T *freeq_entry;
> + dma_addr_t mapping;
> + struct page *page;
> + int i;
> +
> + page = alloc_page(GFP_ATOMIC);
> + if (!page)
> + return NULL;
> +
> + mapping = dma_map_single(geth->dev, page_address(page),
> + PAGE_SIZE, DMA_FROM_DEVICE);
> +
> + if (unlikely(dma_mapping_error(geth->dev, mapping) || !mapping)) {
This should test only dma_mapping_error() since mapping == 0 is valid,
but unlikely.
> +static int geth_setup_freeq(struct gemini_ethernet *geth)
> +{
> + void __iomem *dma_reg = geth->base + GLOBAL_SW_FREEQ_BASE_SIZE_REG;
> + QUEUE_THRESHOLD_T qt;
> + DMA_SKB_SIZE_T skbsz;
> + unsigned int filled;
> + unsigned int frag_len = 1 << geth->freeq_frag_order;
> + unsigned int len = 1 << geth->freeq_order;
> + unsigned int fpp_order = PAGE_SHIFT - geth->freeq_frag_order;
> + unsigned int pages = len >> fpp_order;
> + dma_addr_t mapping;
> + unsigned int pn;
> +
> + geth->freeq_ring = dma_alloc_coherent(geth->dev,
> + sizeof(*geth->freeq_ring) << geth->freeq_order,
> + &geth->freeq_dma_base, GFP_KERNEL);
> + if (!geth->freeq_ring)
> + return -ENOMEM;
> +
> + BUG_ON(geth->freeq_dma_base & ~DMA_Q_BASE_MASK);
Another BUG_ON:
if (WARN_ON(...)) goto err_freeq;
[...]
> +static int gmac_map_tx_bufs(struct net_device *netdev, struct sk_buff *skb,
> + struct gmac_txq *txq, unsigned short *desc)
> +{
[...]
> + if (word1 > mtu) {
> + word1 |= TSS_MTU_ENABLE_BIT;
> + word3 += mtu;
word3 |= mtu; would be more natural.
[...]
> + mapping = dma_map_single(geth->dev, buffer, buflen,
> + DMA_TO_DEVICE);
> + if (dma_mapping_error(geth->dev, mapping) ||
> + !(mapping & PAGE_MASK))
> + goto map_error;
Is page at phys 0 special to the HW?
[...]
> +map_error:
> + while (w != *desc) {
> + w--;
> + w &= m;
> +
> + dma_unmap_page(geth->dev, txq->ring[w].word2.buf_adr,
> + txq->ring[w].word0.bits.buffer_size, DMA_TO_DEVICE);
> + }
> + return ENOMEM;
return -ENOMEM; for consistency, though not used in the caller.
[...]
> +static int gmac_start_xmit(struct sk_buff *skb, struct net_device *netdev)
> +{
[...]
> + if (unlikely(gmac_map_tx_bufs(netdev, skb, txq, &w))) {
> + if (skb_linearize(skb))
> + goto out_drop;
> +
> + if (unlikely(gmac_map_tx_bufs(netdev, skb, txq, &w)))
> + goto out_drop_free;
> +
> + u64_stats_update_begin(&port->tx_stats_syncp);
> + port->tx_frags_linearized++;
> + u64_stats_update_end(&port->tx_stats_syncp);
This misses stats update when mapping after skb_linearize() fails.
[...]
> +static struct sk_buff *gmac_skb_if_good_frame(struct gemini_ethernet_port *port,
> + GMAC_RXDESC_0_T word0, unsigned frame_len)
> +{
> + struct sk_buff *skb = NULL;
> + unsigned rx_status = word0.bits.status;
> + unsigned rx_csum = word0.bits.chksum_status;
> + port->rx_stats[rx_status]++;
> + port->rx_csum_stats[rx_csum]++;
> +
> + if (word0.bits.derr || word0.bits.perr ||
> + rx_status || frame_len < ETH_ZLEN ||
> + rx_csum >= RX_CHKSUM_IP_ERR_UNKNOWN) {
> + port->stats.rx_errors++;
> +
> + if (frame_len < ETH_ZLEN || RX_ERROR_LENGTH(rx_status))
> + port->stats.rx_length_errors++;
> + if (RX_ERROR_OVER(rx_status))
> + port->stats.rx_over_errors++;
> + if (RX_ERROR_CRC(rx_status))
> + port->stats.rx_crc_errors++;
> + if (RX_ERROR_FRAME(rx_status))
> + port->stats.rx_frame_errors++;
> +
> + return NULL;
Could support RXALL feature here.
> + skb = napi_get_frags(&port->napi);
> + if (!skb)
> + return NULL;
This case could get a stats update, too.
> +static unsigned int gmac_rx(struct net_device *netdev, unsigned budget)
> +{
[...]
> + if (unlikely(!mapping)) {
> + netdev_err(netdev,
> + "rxq[%u]: HW BUG: zero DMA desc\n", r);
> + goto err_drop;
> + }
I wonder if this was a bug in the driver or in HW. Does it trigger on
your boxes?
[...]
> +static void gmac_set_rx_mode(struct net_device *netdev)
> +{
> + struct gemini_ethernet_port *port = netdev_priv(netdev);
> + struct netdev_hw_addr *ha;
> + __u32 mc_filter[2];
> + unsigned bit_nr;
> + GMAC_RX_FLTR_T filter = { .bits = {
> + .broadcast = 1,
> + .multicast = 1,
> + .unicast = 1,
> + } };
> +
> + mc_filter[1] = mc_filter[0] = 0;
Looks like this should be = ~0u (IFF_ALLMULTI case).
> + if (netdev->flags & IFF_PROMISC) {
> + filter.bits.error = 1;
> + filter.bits.promiscuous = 1;
> + } else if (!(netdev->flags & IFF_ALLMULTI)) {
> + mc_filter[1] = mc_filter[0] = 0;
> + netdev_for_each_mc_addr(ha, netdev) {
> + bit_nr = ~crc32_le(~0, ha->addr, ETH_ALEN) & 0x3f;
> + mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 0x1f);
> + }
> + }
[...]
More information about the linux-arm-kernel
mailing list