| From 0af5d7d021bb899d9c3880415267e178a20fb7a9 Mon Sep 17 00:00:00 2001 |
| From: Mans Rullgard <mans@mansr.com> |
| Date: Thu, 24 Nov 2016 18:46:41 +0000 |
| Subject: [PATCH] ivshmem-net: fix race in state machine |
| |
| (cherry picked from commit 5d663baed6a89d09bae4446f6509f9957c780bc7) |
| --- |
| drivers/net/ivshmem-net.c | 60 ++++++++++++++++++++++++----------------------- |
| 1 file changed, 31 insertions(+), 29 deletions(-) |
| |
| --- a/drivers/net/ivshmem-net.c |
| +++ b/drivers/net/ivshmem-net.c |
| @@ -36,6 +36,8 @@ |
| #define IVSHM_NET_STATE_READY 2 |
| #define IVSHM_NET_STATE_RUN 3 |
| |
| +#define IVSHM_NET_FLAG_RUN 0 |
| + |
| #define IVSHM_NET_MTU_MIN 256 |
| #define IVSHM_NET_MTU_MAX 65535 |
| #define IVSHM_NET_MTU_DEF 16384 |
| @@ -96,6 +98,8 @@ struct ivshm_net { |
| u32 lstate; |
| u32 rstate; |
| |
| + unsigned long flags; |
| + |
| struct workqueue_struct *state_wq; |
| struct work_struct state_work; |
| |
| @@ -529,12 +533,32 @@ static void ivshm_net_run(struct net_dev |
| { |
| struct ivshm_net *in = netdev_priv(ndev); |
| |
| + if (in->lstate < IVSHM_NET_STATE_READY) |
| + return; |
| + |
| + if (!netif_running(ndev)) |
| + return; |
| + |
| + if (test_and_set_bit(IVSHM_NET_FLAG_RUN, &in->flags)) |
| + return; |
| + |
| netif_start_queue(ndev); |
| napi_enable(&in->napi); |
| napi_schedule(&in->napi); |
| ivshm_net_set_state(in, IVSHM_NET_STATE_RUN); |
| } |
| |
| +static void ivshm_net_do_stop(struct net_device *ndev) |
| +{ |
| + struct ivshm_net *in = netdev_priv(ndev); |
| + |
| + if (!test_and_clear_bit(IVSHM_NET_FLAG_RUN, &in->flags)) |
| + return; |
| + |
| + netif_stop_queue(ndev); |
| + napi_disable(&in->napi); |
| +} |
| + |
| static void ivshm_net_state_change(struct work_struct *work) |
| { |
| struct ivshm_net *in = container_of(work, struct ivshm_net, state_work); |
| @@ -560,21 +584,13 @@ static void ivshm_net_state_change(struc |
| break; |
| |
| case IVSHM_NET_STATE_READY: |
| + case IVSHM_NET_STATE_RUN: |
| if (rstate >= IVSHM_NET_STATE_READY) { |
| netif_carrier_on(ndev); |
| - if (ndev->flags & IFF_UP) |
| - ivshm_net_run(ndev); |
| + ivshm_net_run(ndev); |
| } else { |
| netif_carrier_off(ndev); |
| - ivshm_net_set_state(in, IVSHM_NET_STATE_RESET); |
| - } |
| - break; |
| - |
| - case IVSHM_NET_STATE_RUN: |
| - if (rstate < IVSHM_NET_STATE_READY) { |
| - netif_stop_queue(ndev); |
| - napi_disable(&in->napi); |
| - netif_carrier_off(ndev); |
| + ivshm_net_do_stop(ndev); |
| ivshm_net_set_state(in, IVSHM_NET_STATE_RESET); |
| } |
| break; |
| @@ -584,18 +600,13 @@ static void ivshm_net_state_change(struc |
| WRITE_ONCE(in->rstate, rstate); |
| } |
| |
| -static bool ivshm_net_check_state(struct net_device *ndev) |
| +static void ivshm_net_check_state(struct net_device *ndev) |
| { |
| struct ivshm_net *in = netdev_priv(ndev); |
| u32 rstate = readl(&in->ivshm_regs->rstate); |
| |
| - if (rstate != READ_ONCE(in->rstate) || |
| - in->lstate != IVSHM_NET_STATE_RUN) { |
| + if (rstate != in->rstate || !test_bit(IVSHM_NET_FLAG_RUN, &in->flags)) |
| queue_work(in->state_wq, &in->state_work); |
| - return false; |
| - } |
| - |
| - return true; |
| } |
| |
| static irqreturn_t ivshm_net_int(int irq, void *data) |
| @@ -617,24 +628,15 @@ static int ivshm_net_open(struct net_dev |
| |
| netdev_reset_queue(ndev); |
| ndev->operstate = IF_OPER_UP; |
| - |
| - if (in->lstate == IVSHM_NET_STATE_READY) |
| - ivshm_net_run(ndev); |
| + ivshm_net_run(ndev); |
| |
| return 0; |
| } |
| |
| static int ivshm_net_stop(struct net_device *ndev) |
| { |
| - struct ivshm_net *in = netdev_priv(ndev); |
| - |
| ndev->operstate = IF_OPER_DOWN; |
| - |
| - if (in->lstate == IVSHM_NET_STATE_RUN) { |
| - napi_disable(&in->napi); |
| - netif_stop_queue(ndev); |
| - ivshm_net_set_state(in, IVSHM_NET_STATE_READY); |
| - } |
| + ivshm_net_do_stop(ndev); |
| |
| return 0; |
| } |