Skip to content

Commit 7462012

Browse files
Andri Yngvasonmarckleinebudde
authored andcommitted
can: cc770: Fix queue stall & dropped RTR reply
While waiting for the TX object to send an RTR, an external message with a matching id can overwrite the TX data. In this case we must call the rx routine and then try transmitting the message that was overwritten again. The queue was being stalled because the RX event did not generate an interrupt to wake up the queue again and the TX event did not happen because the TXRQST flag is reset by the chip when new data is received. According to the CC770 datasheet the id of a message object should not be changed while the MSGVAL bit is set. This has been fixed by resetting the MSGVAL bit before modifying the object in the transmit function and setting it after. It is not enough to set & reset CPUUPD. It is important to keep the MSGVAL bit reset while the message object is being modified. Otherwise, during RTR transmission, a frame with matching id could trigger an rx-interrupt, which would cause a race condition between the interrupt routine and the transmit function. Signed-off-by: Andri Yngvason <[email protected]> Tested-by: Richard Weinberger <[email protected]> Cc: linux-stable <[email protected]> Signed-off-by: Marc Kleine-Budde <[email protected]>
1 parent f4353da commit 7462012

File tree

2 files changed

+68
-28
lines changed

2 files changed

+68
-28
lines changed

drivers/net/can/cc770/cc770.c

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -390,37 +390,23 @@ static int cc770_get_berr_counter(const struct net_device *dev,
390390
return 0;
391391
}
392392

393-
static netdev_tx_t cc770_start_xmit(struct sk_buff *skb, struct net_device *dev)
393+
static void cc770_tx(struct net_device *dev, int mo)
394394
{
395395
struct cc770_priv *priv = netdev_priv(dev);
396-
struct net_device_stats *stats = &dev->stats;
397-
struct can_frame *cf = (struct can_frame *)skb->data;
398-
unsigned int mo = obj2msgobj(CC770_OBJ_TX);
396+
struct can_frame *cf = (struct can_frame *)priv->tx_skb->data;
399397
u8 dlc, rtr;
400398
u32 id;
401399
int i;
402400

403-
if (can_dropped_invalid_skb(dev, skb))
404-
return NETDEV_TX_OK;
405-
406-
if ((cc770_read_reg(priv,
407-
msgobj[mo].ctrl1) & TXRQST_UNC) == TXRQST_SET) {
408-
netdev_err(dev, "TX register is still occupied!\n");
409-
return NETDEV_TX_BUSY;
410-
}
411-
412-
netif_stop_queue(dev);
413-
414401
dlc = cf->can_dlc;
415402
id = cf->can_id;
416-
if (cf->can_id & CAN_RTR_FLAG)
417-
rtr = 0;
418-
else
419-
rtr = MSGCFG_DIR;
403+
rtr = cf->can_id & CAN_RTR_FLAG ? 0 : MSGCFG_DIR;
404+
405+
cc770_write_reg(priv, msgobj[mo].ctrl0,
406+
MSGVAL_RES | TXIE_RES | RXIE_RES | INTPND_RES);
420407
cc770_write_reg(priv, msgobj[mo].ctrl1,
421408
RMTPND_RES | TXRQST_RES | CPUUPD_SET | NEWDAT_RES);
422-
cc770_write_reg(priv, msgobj[mo].ctrl0,
423-
MSGVAL_SET | TXIE_SET | RXIE_RES | INTPND_RES);
409+
424410
if (id & CAN_EFF_FLAG) {
425411
id &= CAN_EFF_MASK;
426412
cc770_write_reg(priv, msgobj[mo].config,
@@ -439,13 +425,30 @@ static netdev_tx_t cc770_start_xmit(struct sk_buff *skb, struct net_device *dev)
439425
for (i = 0; i < dlc; i++)
440426
cc770_write_reg(priv, msgobj[mo].data[i], cf->data[i]);
441427

442-
/* Store echo skb before starting the transfer */
443-
can_put_echo_skb(skb, dev, 0);
444-
445428
cc770_write_reg(priv, msgobj[mo].ctrl1,
446-
RMTPND_RES | TXRQST_SET | CPUUPD_RES | NEWDAT_UNC);
429+
RMTPND_UNC | TXRQST_SET | CPUUPD_RES | NEWDAT_UNC);
430+
cc770_write_reg(priv, msgobj[mo].ctrl0,
431+
MSGVAL_SET | TXIE_SET | RXIE_SET | INTPND_UNC);
432+
}
433+
434+
static netdev_tx_t cc770_start_xmit(struct sk_buff *skb, struct net_device *dev)
435+
{
436+
struct cc770_priv *priv = netdev_priv(dev);
437+
unsigned int mo = obj2msgobj(CC770_OBJ_TX);
438+
439+
if (can_dropped_invalid_skb(dev, skb))
440+
return NETDEV_TX_OK;
447441

448-
stats->tx_bytes += dlc;
442+
netif_stop_queue(dev);
443+
444+
if ((cc770_read_reg(priv,
445+
msgobj[mo].ctrl1) & TXRQST_UNC) == TXRQST_SET) {
446+
netdev_err(dev, "TX register is still occupied!\n");
447+
return NETDEV_TX_BUSY;
448+
}
449+
450+
priv->tx_skb = skb;
451+
cc770_tx(dev, mo);
449452

450453
return NETDEV_TX_OK;
451454
}
@@ -671,13 +674,47 @@ static void cc770_tx_interrupt(struct net_device *dev, unsigned int o)
671674
struct cc770_priv *priv = netdev_priv(dev);
672675
struct net_device_stats *stats = &dev->stats;
673676
unsigned int mo = obj2msgobj(o);
677+
struct can_frame *cf;
678+
u8 ctrl1;
679+
680+
ctrl1 = cc770_read_reg(priv, msgobj[mo].ctrl1);
674681

675-
/* Nothing more to send, switch off interrupts */
676682
cc770_write_reg(priv, msgobj[mo].ctrl0,
677683
MSGVAL_RES | TXIE_RES | RXIE_RES | INTPND_RES);
684+
cc770_write_reg(priv, msgobj[mo].ctrl1,
685+
RMTPND_RES | TXRQST_RES | MSGLST_RES | NEWDAT_RES);
678686

679-
stats->tx_packets++;
687+
if (unlikely(!priv->tx_skb)) {
688+
netdev_err(dev, "missing tx skb in tx interrupt\n");
689+
return;
690+
}
691+
692+
if (unlikely(ctrl1 & MSGLST_SET)) {
693+
stats->rx_over_errors++;
694+
stats->rx_errors++;
695+
}
696+
697+
/* When the CC770 is sending an RTR message and it receives a regular
698+
* message that matches the id of the RTR message, it will overwrite the
699+
* outgoing message in the TX register. When this happens we must
700+
* process the received message and try to transmit the outgoing skb
701+
* again.
702+
*/
703+
if (unlikely(ctrl1 & NEWDAT_SET)) {
704+
cc770_rx(dev, mo, ctrl1);
705+
cc770_tx(dev, mo);
706+
return;
707+
}
708+
709+
can_put_echo_skb(priv->tx_skb, dev, 0);
680710
can_get_echo_skb(dev, 0);
711+
712+
cf = (struct can_frame *)priv->tx_skb->data;
713+
stats->tx_bytes += cf->can_dlc;
714+
stats->tx_packets++;
715+
716+
priv->tx_skb = NULL;
717+
681718
netif_wake_queue(dev);
682719
}
683720

@@ -789,6 +826,7 @@ struct net_device *alloc_cc770dev(int sizeof_priv)
789826
priv->can.do_set_bittiming = cc770_set_bittiming;
790827
priv->can.do_set_mode = cc770_set_mode;
791828
priv->can.ctrlmode_supported = CAN_CTRLMODE_3_SAMPLES;
829+
priv->tx_skb = NULL;
792830

793831
memcpy(priv->obj_flags, cc770_obj_flags, sizeof(cc770_obj_flags));
794832

drivers/net/can/cc770/cc770.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ struct cc770_priv {
193193
u8 cpu_interface; /* CPU interface register */
194194
u8 clkout; /* Clock out register */
195195
u8 bus_config; /* Bus conffiguration register */
196+
197+
struct sk_buff *tx_skb;
196198
};
197199

198200
struct net_device *alloc_cc770dev(int sizeof_priv);

0 commit comments

Comments
 (0)