Commit 91e9f2c0 authored by Anssi Hannula's avatar Anssi Hannula Committed by Marc Kleine-Budde

can: xilinx_can: avoid non-requested bus error frames

Userspace can signal with CAN_CTRLMODE_BERR_REPORTING whether they need
reporting of bus errors (CAN_ERR_BUSERROR) or not.

However, xilinx_can driver currently always sends CAN_ERR_BUSERROR
frames to userspace on bus errors.

To improve performance on error conditions when bus error reporting is
not needed, avoid sending CAN_ERR_BUSERROR frames unless requested via
CAN_CTRLMODE_BERR_REPORTING.

The error interrupt is still kept enabled as there is no dedicated state
transition interrupt, but just disabling error frame submission still
yields a significant performance improvement. In a simple test with
continuous bus errors and no userspace programs reading/writing CAN I
saw system CPU load reduced by 1/3.

Tested on a ZynqMP board with CAN-FD v1.0.
Signed-off-by: default avatarAnssi Hannula <anssi.hannula@bitwise.fi>
Signed-off-by: default avatarMarc Kleine-Budde <mkl@pengutronix.de>
parent b687a779
...@@ -470,7 +470,13 @@ static int xcan_chip_start(struct net_device *ndev) ...@@ -470,7 +470,13 @@ static int xcan_chip_start(struct net_device *ndev)
if (err < 0) if (err < 0)
return err; return err;
/* Enable interrupts */ /* Enable interrupts
*
* We enable the ERROR interrupt even with
* CAN_CTRLMODE_BERR_REPORTING disabled as there is no
* dedicated interrupt for a state change to
* ERROR_WARNING/ERROR_PASSIVE.
*/
ier = XCAN_IXR_TXOK_MASK | XCAN_IXR_BSOFF_MASK | ier = XCAN_IXR_TXOK_MASK | XCAN_IXR_BSOFF_MASK |
XCAN_IXR_WKUP_MASK | XCAN_IXR_SLP_MASK | XCAN_IXR_WKUP_MASK | XCAN_IXR_SLP_MASK |
XCAN_IXR_ERROR_MASK | XCAN_IXR_RXOFLW_MASK | XCAN_IXR_ERROR_MASK | XCAN_IXR_RXOFLW_MASK |
...@@ -980,12 +986,9 @@ static void xcan_err_interrupt(struct net_device *ndev, u32 isr) ...@@ -980,12 +986,9 @@ static void xcan_err_interrupt(struct net_device *ndev, u32 isr)
{ {
struct xcan_priv *priv = netdev_priv(ndev); struct xcan_priv *priv = netdev_priv(ndev);
struct net_device_stats *stats = &ndev->stats; struct net_device_stats *stats = &ndev->stats;
struct can_frame *cf; struct can_frame cf = { };
struct sk_buff *skb;
u32 err_status; u32 err_status;
skb = alloc_can_err_skb(ndev, &cf);
err_status = priv->read_reg(priv, XCAN_ESR_OFFSET); err_status = priv->read_reg(priv, XCAN_ESR_OFFSET);
priv->write_reg(priv, XCAN_ESR_OFFSET, err_status); priv->write_reg(priv, XCAN_ESR_OFFSET, err_status);
...@@ -995,32 +998,27 @@ static void xcan_err_interrupt(struct net_device *ndev, u32 isr) ...@@ -995,32 +998,27 @@ static void xcan_err_interrupt(struct net_device *ndev, u32 isr)
/* Leave device in Config Mode in bus-off state */ /* Leave device in Config Mode in bus-off state */
priv->write_reg(priv, XCAN_SRR_OFFSET, XCAN_SRR_RESET_MASK); priv->write_reg(priv, XCAN_SRR_OFFSET, XCAN_SRR_RESET_MASK);
can_bus_off(ndev); can_bus_off(ndev);
if (skb) cf.can_id |= CAN_ERR_BUSOFF;
cf->can_id |= CAN_ERR_BUSOFF;
} else { } else {
enum can_state new_state = xcan_current_error_state(ndev); enum can_state new_state = xcan_current_error_state(ndev);
if (new_state != priv->can.state) if (new_state != priv->can.state)
xcan_set_error_state(ndev, new_state, skb ? cf : NULL); xcan_set_error_state(ndev, new_state, &cf);
} }
/* Check for Arbitration lost interrupt */ /* Check for Arbitration lost interrupt */
if (isr & XCAN_IXR_ARBLST_MASK) { if (isr & XCAN_IXR_ARBLST_MASK) {
priv->can.can_stats.arbitration_lost++; priv->can.can_stats.arbitration_lost++;
if (skb) { cf.can_id |= CAN_ERR_LOSTARB;
cf->can_id |= CAN_ERR_LOSTARB; cf.data[0] = CAN_ERR_LOSTARB_UNSPEC;
cf->data[0] = CAN_ERR_LOSTARB_UNSPEC;
}
} }
/* Check for RX FIFO Overflow interrupt */ /* Check for RX FIFO Overflow interrupt */
if (isr & XCAN_IXR_RXOFLW_MASK) { if (isr & XCAN_IXR_RXOFLW_MASK) {
stats->rx_over_errors++; stats->rx_over_errors++;
stats->rx_errors++; stats->rx_errors++;
if (skb) { cf.can_id |= CAN_ERR_CRTL;
cf->can_id |= CAN_ERR_CRTL; cf.data[1] |= CAN_ERR_CRTL_RX_OVERFLOW;
cf->data[1] |= CAN_ERR_CRTL_RX_OVERFLOW;
}
} }
/* Check for RX Match Not Finished interrupt */ /* Check for RX Match Not Finished interrupt */
...@@ -1028,69 +1026,78 @@ static void xcan_err_interrupt(struct net_device *ndev, u32 isr) ...@@ -1028,69 +1026,78 @@ static void xcan_err_interrupt(struct net_device *ndev, u32 isr)
stats->rx_dropped++; stats->rx_dropped++;
stats->rx_errors++; stats->rx_errors++;
netdev_err(ndev, "RX match not finished, frame discarded\n"); netdev_err(ndev, "RX match not finished, frame discarded\n");
if (skb) { cf.can_id |= CAN_ERR_CRTL;
cf->can_id |= CAN_ERR_CRTL; cf.data[1] |= CAN_ERR_CRTL_UNSPEC;
cf->data[1] |= CAN_ERR_CRTL_UNSPEC;
}
} }
/* Check for error interrupt */ /* Check for error interrupt */
if (isr & XCAN_IXR_ERROR_MASK) { if (isr & XCAN_IXR_ERROR_MASK) {
if (skb) bool berr_reporting = false;
cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) {
berr_reporting = true;
cf.can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
}
/* Check for Ack error interrupt */ /* Check for Ack error interrupt */
if (err_status & XCAN_ESR_ACKER_MASK) { if (err_status & XCAN_ESR_ACKER_MASK) {
stats->tx_errors++; stats->tx_errors++;
if (skb) { if (berr_reporting) {
cf->can_id |= CAN_ERR_ACK; cf.can_id |= CAN_ERR_ACK;
cf->data[3] = CAN_ERR_PROT_LOC_ACK; cf.data[3] = CAN_ERR_PROT_LOC_ACK;
} }
} }
/* Check for Bit error interrupt */ /* Check for Bit error interrupt */
if (err_status & XCAN_ESR_BERR_MASK) { if (err_status & XCAN_ESR_BERR_MASK) {
stats->tx_errors++; stats->tx_errors++;
if (skb) { if (berr_reporting) {
cf->can_id |= CAN_ERR_PROT; cf.can_id |= CAN_ERR_PROT;
cf->data[2] = CAN_ERR_PROT_BIT; cf.data[2] = CAN_ERR_PROT_BIT;
} }
} }
/* Check for Stuff error interrupt */ /* Check for Stuff error interrupt */
if (err_status & XCAN_ESR_STER_MASK) { if (err_status & XCAN_ESR_STER_MASK) {
stats->rx_errors++; stats->rx_errors++;
if (skb) { if (berr_reporting) {
cf->can_id |= CAN_ERR_PROT; cf.can_id |= CAN_ERR_PROT;
cf->data[2] = CAN_ERR_PROT_STUFF; cf.data[2] = CAN_ERR_PROT_STUFF;
} }
} }
/* Check for Form error interrupt */ /* Check for Form error interrupt */
if (err_status & XCAN_ESR_FMER_MASK) { if (err_status & XCAN_ESR_FMER_MASK) {
stats->rx_errors++; stats->rx_errors++;
if (skb) { if (berr_reporting) {
cf->can_id |= CAN_ERR_PROT; cf.can_id |= CAN_ERR_PROT;
cf->data[2] = CAN_ERR_PROT_FORM; cf.data[2] = CAN_ERR_PROT_FORM;
} }
} }
/* Check for CRC error interrupt */ /* Check for CRC error interrupt */
if (err_status & XCAN_ESR_CRCER_MASK) { if (err_status & XCAN_ESR_CRCER_MASK) {
stats->rx_errors++; stats->rx_errors++;
if (skb) { if (berr_reporting) {
cf->can_id |= CAN_ERR_PROT; cf.can_id |= CAN_ERR_PROT;
cf->data[3] = CAN_ERR_PROT_LOC_CRC_SEQ; cf.data[3] = CAN_ERR_PROT_LOC_CRC_SEQ;
} }
} }
priv->can.can_stats.bus_error++; priv->can.can_stats.bus_error++;
} }
if (cf.can_id) {
struct can_frame *skb_cf;
struct sk_buff *skb = alloc_can_err_skb(ndev, &skb_cf);
if (skb) { if (skb) {
skb_cf->can_id |= cf.can_id;
memcpy(skb_cf->data, cf.data, CAN_ERR_DLC);
stats->rx_packets++; stats->rx_packets++;
stats->rx_bytes += cf->can_dlc; stats->rx_bytes += CAN_ERR_DLC;
netif_rx(skb); netif_rx(skb);
} }
}
netdev_dbg(ndev, "%s: error status register:0x%x\n", netdev_dbg(ndev, "%s: error status register:0x%x\n",
__func__, priv->read_reg(priv, XCAN_ESR_OFFSET)); __func__, priv->read_reg(priv, XCAN_ESR_OFFSET));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment