From patchwork Thu Apr 7 16:16:30 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Akinobu Mita X-Patchwork-Id: 607491 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming@ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3qgnmD2vV5z9t5q for ; Fri, 8 Apr 2016 02:17:00 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b=nFV3iZxK; dkim-atps=neutral Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756199AbcDGQQ4 (ORCPT ); Thu, 7 Apr 2016 12:16:56 -0400 Received: from mail-pa0-f67.google.com ([209.85.220.67]:34735 "EHLO mail-pa0-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755498AbcDGQQy (ORCPT ); Thu, 7 Apr 2016 12:16:54 -0400 Received: by mail-pa0-f67.google.com with SMTP id hb4so7164850pac.1 for ; Thu, 07 Apr 2016 09:16:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=RTkgu9Aux3Qv0HQUu8p5Q32FYmCzsUGBziZx6recZok=; b=nFV3iZxK1NwjGmjMTcgBWpguu7hDMTHG+O3Up5PHy3nCCHNCgt43Lp6aI6R3UbaKju OCGzplgPBibLbwF/g4xJIownia2XbUrXva75MtD0T299BE1k58iC0VnldUf3UmpO24Ep ClYIJKjxiPmLeaejMf94dAFPFGtUScHDCOV2Rabr2KUzb2w5PsxQzzTfpZBmqVUpxBI+ PXT8uGhHdl7HSLkYxZ26hXweR3cPw0CPrSAwWtz4nFpJEEA/DDOuoWS3u1JZub3bFjeN jpgMIfXAJWSm3gbPx+s4QMHFl5PfgJpxjCnewP41Tt7UKkAe/Wcbe8GqyksbNL4yO6bg dbLQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=RTkgu9Aux3Qv0HQUu8p5Q32FYmCzsUGBziZx6recZok=; b=ZpNI0cQmszlhqEKsHLiAg9EkKCLTyhWuwmlo4JrNqjY1XYk8FbYqYRpihXLxZ6wTfV 7KawrRAhjhi2pj1XcrzPsA2y+6Dh6eA1yhiztJNhSWlOfF7oAKp0A4znKBPQCGjheTN+ 8FRLUloMT3VEso8r7/gECHtsIzDJ5t83kEZ68y2ABIo6gfAYJ9QNg4oHC5ehX1j238Mm /oggdOXDwAP6O0AIkWYxPzA/YKhWke9eL37LU4DfMc4WbB5jjF73CiG1Nan9bD/LQ3Ri RzJtHB6+6tJzeyR78bNHeazV0wKe9AJzilchKs8M4XIj7h32PFhXZDnaqfhpikXeK3Cs XC6Q== X-Gm-Message-State: AD7BkJL1cif/fUlk7N0ziIjjb8CzQLil25NClPXPzgKdwDUo89yYlIPhrNGdd2+mTiMzDg== X-Received: by 10.66.122.3 with SMTP id lo3mr5710670pab.25.1460045813502; Thu, 07 Apr 2016 09:16:53 -0700 (PDT) Received: from localhost.localdomain (KD113159139091.ppp-bb.dion.ne.jp. [113.159.139.91]) by smtp.gmail.com with ESMTPSA id i1sm13246045pfj.17.2016.04.07.09.16.51 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Thu, 07 Apr 2016 09:16:52 -0700 (PDT) From: Akinobu Mita To: netdev@vger.kernel.org Cc: Akinobu Mita , Mike Sinkovsky , "David S. Miller" Subject: [PATCH v2 3/5] net: w5100: enable to support sleepable register access interface Date: Fri, 8 Apr 2016 01:16:30 +0900 Message-Id: <1460045792-30594-3-git-send-email-akinobu.mita@gmail.com> X-Mailer: git-send-email 2.5.0 In-Reply-To: <1460045792-30594-1-git-send-email-akinobu.mita@gmail.com> References: <1460045792-30594-1-git-send-email-akinobu.mita@gmail.com> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org SPI transfer routines are callable only from contexts that can sleep. This adds ability to tell the core driver that the interface mode cannot access w5100 register on atomic contexts. In this case, workqueue and threaded irq are required. This also corrects timeout period waiting for command register to be automatically cleared because the latency of the register access with SPI transfer can be interfered by other contexts. Signed-off-by: Akinobu Mita Cc: Mike Sinkovsky Cc: David S. Miller --- * v2 - Use a plain single pointer instead of SKB queue, spotted by David S. Miller - Correct timeout period in w5100_command drivers/net/ethernet/wiznet/w5100.c | 190 ++++++++++++++++++++++++++++-------- drivers/net/ethernet/wiznet/w5100.h | 1 + 2 files changed, 153 insertions(+), 38 deletions(-) diff --git a/drivers/net/ethernet/wiznet/w5100.c b/drivers/net/ethernet/wiznet/w5100.c index 7290096..c13f43d 100644 --- a/drivers/net/ethernet/wiznet/w5100.c +++ b/drivers/net/ethernet/wiznet/w5100.c @@ -102,6 +102,13 @@ struct w5100_priv { bool promisc; u32 msg_enable; + struct workqueue_struct *xfer_wq; + struct work_struct rx_work; + struct sk_buff *tx_skb; + struct work_struct tx_work; + struct work_struct setrx_work; + struct work_struct restart_work; + struct w5100_mmio_priv mmio_priv; }; @@ -505,10 +512,12 @@ static int w5100_reset(struct w5100_priv *priv) static int w5100_command(struct w5100_priv *priv, u16 cmd) { - unsigned long timeout = jiffies + msecs_to_jiffies(100); + unsigned long timeout; w5100_write(priv, W5100_S0_CR, cmd); + timeout = jiffies + msecs_to_jiffies(100); + while (w5100_read(priv, W5100_S0_CR) != 0) { if (time_after(jiffies, timeout)) return -EIO; @@ -608,7 +617,7 @@ static void w5100_get_regs(struct net_device *ndev, w5100_readbulk(priv, W5100_S0_REGS, buf, W5100_S0_REGS_LEN); } -static void w5100_tx_timeout(struct net_device *ndev) +static void w5100_restart(struct net_device *ndev) { struct w5100_priv *priv = netdev_priv(ndev); @@ -620,12 +629,28 @@ static void w5100_tx_timeout(struct net_device *ndev) netif_wake_queue(ndev); } -static int w5100_start_tx(struct sk_buff *skb, struct net_device *ndev) +static void w5100_restart_work(struct work_struct *work) +{ + struct w5100_priv *priv = container_of(work, struct w5100_priv, + restart_work); + + w5100_restart(priv->ndev); +} + +static void w5100_tx_timeout(struct net_device *ndev) { struct w5100_priv *priv = netdev_priv(ndev); - u16 offset; - netif_stop_queue(ndev); + if (priv->ops->may_sleep) + schedule_work(&priv->restart_work); + else + w5100_restart(ndev); +} + +static void w5100_tx_skb(struct net_device *ndev, struct sk_buff *skb) +{ + struct w5100_priv *priv = netdev_priv(ndev); + u16 offset; offset = w5100_read16(priv, W5100_S0_TX_WR); w5100_writebuf(priv, offset, skb->data, skb->len); @@ -635,47 +660,98 @@ static int w5100_start_tx(struct sk_buff *skb, struct net_device *ndev) dev_kfree_skb(skb); w5100_command(priv, S0_CR_SEND); +} + +static void w5100_tx_work(struct work_struct *work) +{ + struct w5100_priv *priv = container_of(work, struct w5100_priv, + tx_work); + struct sk_buff *skb = priv->tx_skb; + + priv->tx_skb = NULL; + + if (WARN_ON(!skb)) + return; + w5100_tx_skb(priv->ndev, skb); +} + +static int w5100_start_tx(struct sk_buff *skb, struct net_device *ndev) +{ + struct w5100_priv *priv = netdev_priv(ndev); + + netif_stop_queue(ndev); + + if (priv->ops->may_sleep) { + WARN_ON(priv->tx_skb); + priv->tx_skb = skb; + queue_work(priv->xfer_wq, &priv->tx_work); + } else { + w5100_tx_skb(ndev, skb); + } return NETDEV_TX_OK; } -static int w5100_napi_poll(struct napi_struct *napi, int budget) +static struct sk_buff *w5100_rx_skb(struct net_device *ndev) { - struct w5100_priv *priv = container_of(napi, struct w5100_priv, napi); - struct net_device *ndev = priv->ndev; + struct w5100_priv *priv = netdev_priv(ndev); struct sk_buff *skb; - int rx_count; u16 rx_len; u16 offset; u8 header[2]; + u16 rx_buf_len = w5100_read16(priv, W5100_S0_RX_RSR); - for (rx_count = 0; rx_count < budget; rx_count++) { - u16 rx_buf_len = w5100_read16(priv, W5100_S0_RX_RSR); - if (rx_buf_len == 0) - break; + if (rx_buf_len == 0) + return NULL; - offset = w5100_read16(priv, W5100_S0_RX_RD); - w5100_readbuf(priv, offset, header, 2); - rx_len = get_unaligned_be16(header) - 2; - - skb = netdev_alloc_skb_ip_align(ndev, rx_len); - if (unlikely(!skb)) { - w5100_write16(priv, W5100_S0_RX_RD, - offset + rx_buf_len); - w5100_command(priv, S0_CR_RECV); - ndev->stats.rx_dropped++; - return -ENOMEM; - } + offset = w5100_read16(priv, W5100_S0_RX_RD); + w5100_readbuf(priv, offset, header, 2); + rx_len = get_unaligned_be16(header) - 2; - skb_put(skb, rx_len); - w5100_readbuf(priv, offset + 2, skb->data, rx_len); - w5100_write16(priv, W5100_S0_RX_RD, offset + 2 + rx_len); + skb = netdev_alloc_skb_ip_align(ndev, rx_len); + if (unlikely(!skb)) { + w5100_write16(priv, W5100_S0_RX_RD, offset + rx_buf_len); w5100_command(priv, S0_CR_RECV); - skb->protocol = eth_type_trans(skb, ndev); + ndev->stats.rx_dropped++; + return NULL; + } + + skb_put(skb, rx_len); + w5100_readbuf(priv, offset + 2, skb->data, rx_len); + w5100_write16(priv, W5100_S0_RX_RD, offset + 2 + rx_len); + w5100_command(priv, S0_CR_RECV); + skb->protocol = eth_type_trans(skb, ndev); + + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += rx_len; + + return skb; +} + +static void w5100_rx_work(struct work_struct *work) +{ + struct w5100_priv *priv = container_of(work, struct w5100_priv, + rx_work); + struct sk_buff *skb; + + while ((skb = w5100_rx_skb(priv->ndev))) + netif_rx_ni(skb); + + w5100_write(priv, W5100_IMR, IR_S0); +} + +static int w5100_napi_poll(struct napi_struct *napi, int budget) +{ + struct w5100_priv *priv = container_of(napi, struct w5100_priv, napi); + int rx_count; + + for (rx_count = 0; rx_count < budget; rx_count++) { + struct sk_buff *skb = w5100_rx_skb(priv->ndev); - netif_receive_skb(skb); - ndev->stats.rx_packets++; - ndev->stats.rx_bytes += rx_len; + if (skb) + netif_receive_skb(skb); + else + break; } if (rx_count < budget) { @@ -702,10 +778,12 @@ static irqreturn_t w5100_interrupt(int irq, void *ndev_instance) } if (ir & S0_IR_RECV) { - if (napi_schedule_prep(&priv->napi)) { - w5100_write(priv, W5100_IMR, 0); + w5100_write(priv, W5100_IMR, 0); + + if (priv->ops->may_sleep) + queue_work(priv->xfer_wq, &priv->rx_work); + else if (napi_schedule_prep(&priv->napi)) __napi_schedule(&priv->napi); - } } return IRQ_HANDLED; @@ -729,6 +807,14 @@ static irqreturn_t w5100_detect_link(int irq, void *ndev_instance) return IRQ_HANDLED; } +static void w5100_setrx_work(struct work_struct *work) +{ + struct w5100_priv *priv = container_of(work, struct w5100_priv, + setrx_work); + + w5100_hw_start(priv); +} + static void w5100_set_rx_mode(struct net_device *ndev) { struct w5100_priv *priv = netdev_priv(ndev); @@ -736,7 +822,11 @@ static void w5100_set_rx_mode(struct net_device *ndev) if (priv->promisc != set_promisc) { priv->promisc = set_promisc; - w5100_hw_start(priv); + + if (priv->ops->may_sleep) + schedule_work(&priv->setrx_work); + else + w5100_hw_start(priv); } } @@ -860,6 +950,17 @@ int w5100_probe(struct device *dev, const struct w5100_ops *ops, u8 *mac_addr, if (err < 0) goto err_register; + priv->xfer_wq = create_workqueue(netdev_name(ndev)); + if (!priv->xfer_wq) { + err = -ENOMEM; + goto err_wq; + } + + INIT_WORK(&priv->rx_work, w5100_rx_work); + INIT_WORK(&priv->tx_work, w5100_tx_work); + INIT_WORK(&priv->setrx_work, w5100_setrx_work); + INIT_WORK(&priv->restart_work, w5100_restart_work); + if (mac_addr) memcpy(ndev->dev_addr, mac_addr, ETH_ALEN); else @@ -877,8 +978,14 @@ int w5100_probe(struct device *dev, const struct w5100_ops *ops, u8 *mac_addr, goto err_hw; } - err = request_irq(priv->irq, w5100_interrupt, IRQF_TRIGGER_LOW, - netdev_name(ndev), ndev); + if (ops->may_sleep) { + err = request_threaded_irq(priv->irq, NULL, w5100_interrupt, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + netdev_name(ndev), ndev); + } else { + err = request_irq(priv->irq, w5100_interrupt, + IRQF_TRIGGER_LOW, netdev_name(ndev), ndev); + } if (err) goto err_hw; @@ -903,6 +1010,8 @@ int w5100_probe(struct device *dev, const struct w5100_ops *ops, u8 *mac_addr, err_gpio: free_irq(priv->irq, ndev); err_hw: + destroy_workqueue(priv->xfer_wq); +err_wq: unregister_netdev(ndev); err_register: free_netdev(ndev); @@ -920,6 +1029,11 @@ int w5100_remove(struct device *dev) if (gpio_is_valid(priv->link_gpio)) free_irq(priv->link_irq, ndev); + flush_work(&priv->setrx_work); + flush_work(&priv->restart_work); + flush_workqueue(priv->xfer_wq); + destroy_workqueue(priv->xfer_wq); + unregister_netdev(ndev); free_netdev(ndev); return 0; diff --git a/drivers/net/ethernet/wiznet/w5100.h b/drivers/net/ethernet/wiznet/w5100.h index 368862d..292fbd5 100644 --- a/drivers/net/ethernet/wiznet/w5100.h +++ b/drivers/net/ethernet/wiznet/w5100.h @@ -8,6 +8,7 @@ */ struct w5100_ops { + bool may_sleep; int (*read)(struct net_device *ndev, u16 addr); int (*write)(struct net_device *ndev, u16 addr, u8 data); int (*read16)(struct net_device *ndev, u16 addr);