Hi Marc and everyone,
third version part 2, functionally I had to move from spin_lock to
spin_lock_irqsave because of an interrupt that was calling start_xmit,
see attached stack. This is tested on tcan455x but I don't have the
integrated hardware myself so any testing is appreciated.
The series implements many small and bigger throughput improvements and
adds rx/tx coalescing at the end.
Best,
Markus
Changes in v3:
- Remove parenthesis in error messages
- Use memcpy_and_pad for buffer copy in 'can: m_can: Write transmit
header and data in one transaction'.
- Replace spin_lock with spin_lock_irqsave. I got a report of a
interrupt that was calling start_xmit just after the netqueue was
woken up before the locked region was exited. spin_lock_irqsave should
fix this. I attached the full stack at the end of the mail if someone
wants to know.
- Rebased to v6.3-rc1.
- Removed tcan4x5x patches from this series.
Changes in v2:
- Rebased on v6.2-rc5
- Fixed missing/broken accounting for non peripheral m_can devices.
part 1:
v1 - https://lore.kernel.org/lkml/[email protected]
v2 - https://lore.kernel.org/lkml/[email protected]
part 2:
v1 - https://lore.kernel.org/lkml/[email protected]
v2 - https://lore.kernel.org/lkml/[email protected]
stack of calling start_xmit within locked region:
[ 308.170171] dump_backtrace+0x0/0x1a0
[ 308.173841] show_stack+0x18/0x70
[ 308.177158] sched_show_task+0x154/0x180
[ 308.181084] dump_cpu_task+0x44/0x54
[ 308.184664] rcu_dump_cpu_stacks+0xe8/0x12c
[ 308.188846] rcu_sched_clock_irq+0x9f4/0xd10
[ 308.193118] update_process_times+0x9c/0xec
[ 308.197304] tick_sched_handle+0x34/0x60
[ 308.201231] tick_sched_timer+0x4c/0xa4
[ 308.205071] __hrtimer_run_queues+0x138/0x1e0
[ 308.209429] hrtimer_interrupt+0xe8/0x244
[ 308.213440] arch_timer_handler_phys+0x38/0x50
[ 308.217890] handle_percpu_devid_irq+0x84/0x130
[ 308.222422] handle_domain_irq+0x60/0x90
[ 308.226347] gic_handle_irq+0x54/0x130
[ 308.230099] do_interrupt_handler+0x34/0x60
[ 308.234286] el1_interrupt+0x30/0x80
[ 308.237861] el1h_64_irq_handler+0x18/0x24
[ 308.241958] el1h_64_irq+0x78/0x7c
[ 308.245360] queued_spin_lock_slowpath+0xf4/0x390
[ 308.250067] m_can_start_tx+0x20/0xb0 [m_can]
[ 308.254431] m_can_start_xmit+0xd8/0x230 [m_can]
[ 308.259054] dev_hard_start_xmit+0xd4/0x15c
[ 308.263241] sch_direct_xmit+0xe8/0x370
[ 308.267080] __qdisc_run+0x118/0x650
[ 308.270660] net_tx_action+0x118/0x230
[ 308.274409] _stext+0x124/0x2a0
[ 308.277549] __irq_exit_rcu+0xe4/0x100
[ 308.281302] irq_exit+0x10/0x20
[ 308.284444] handle_domain_irq+0x64/0x90
[ 308.288367] gic_handle_irq+0x54/0x130
[ 308.292119] call_on_irq_stack+0x2c/0x54
[ 308.296043] do_interrupt_handler+0x54/0x60
[ 308.300228] el1_interrupt+0x30/0x80
[ 308.303804] el1h_64_irq_handler+0x18/0x24
[ 308.307901] el1h_64_irq+0x78/0x7c
[ 308.311303] __netif_schedule+0x78/0xa0
[ 308.315138] netif_tx_wake_queue+0x50/0x7c
[ 308.319237] m_can_isr+0x474/0x1710 [m_can]
[ 308.323425] irq_thread_fn+0x2c/0x9c
[ 308.327005] irq_thread+0x178/0x2c0
[ 308.330497] kthread+0x150/0x160
[ 308.333727] ret_from_fork+0x10/0x20
Markus Schneider-Pargmann (16):
can: m_can: Remove repeated check for is_peripheral
can: m_can: Always acknowledge all interrupts
can: m_can: Remove double interrupt enable
can: m_can: Disable unused interrupts
can: m_can: Keep interrupts enabled during peripheral read
can: m_can: Write transmit header and data in one transaction
can: m_can: Implement receive coalescing
can: m_can: Implement transmit coalescing
can: m_can: Add rx coalescing ethtool support
can: m_can: Add tx coalescing ethtool support
can: m_can: Cache tx putidx
can: m_can: Use the workqueue as queue
can: m_can: Introduce a tx_fifo_in_flight counter
can: m_can: Use tx_fifo_in_flight for netif_queue control
can: m_can: Implement BQL
can: m_can: Implement transmit submission coalescing
drivers/net/can/m_can/m_can.c | 503 ++++++++++++++++++++++++++--------
drivers/net/can/m_can/m_can.h | 35 ++-
2 files changed, 415 insertions(+), 123 deletions(-)
--
2.39.2
Merge both if-blocks to fix this.
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
drivers/net/can/m_can/m_can.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index 8e83d6963d85..563625a701fc 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -1592,10 +1592,8 @@ static int m_can_close(struct net_device *dev)
cdev->tx_skb = NULL;
destroy_workqueue(cdev->tx_wq);
cdev->tx_wq = NULL;
- }
-
- if (cdev->is_peripheral)
can_rx_offload_disable(&cdev->offload);
+ }
close_candev(dev);
--
2.39.2
There are a number of interrupts that are not used by the driver at the
moment. Disable all of these.
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
drivers/net/can/m_can/m_can.c | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index 5274d9642566..e7aceeba3759 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -1261,6 +1261,7 @@ static int m_can_set_bittiming(struct net_device *dev)
static int m_can_chip_config(struct net_device *dev)
{
struct m_can_classdev *cdev = netdev_priv(dev);
+ u32 interrupts = IR_ALL_INT;
u32 cccr, test;
int err;
@@ -1270,6 +1271,11 @@ static int m_can_chip_config(struct net_device *dev)
return err;
}
+ /* Disable unused interrupts */
+ interrupts &= ~(IR_ARA | IR_ELO | IR_DRX | IR_TEFF | IR_TEFW | IR_TFE |
+ IR_TCF | IR_HPM | IR_RF1F | IR_RF1W | IR_RF1N |
+ IR_RF0F | IR_RF0W);
+
m_can_config_endisable(cdev, true);
/* RX Buffer/FIFO Element Size 64 bytes data field */
@@ -1364,15 +1370,13 @@ static int m_can_chip_config(struct net_device *dev)
m_can_write(cdev, M_CAN_TEST, test);
/* Enable interrupts */
- if (!(cdev->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING))
+ if (!(cdev->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)) {
if (cdev->version == 30)
- m_can_write(cdev, M_CAN_IE, IR_ALL_INT &
- ~(IR_ERR_LEC_30X));
+ interrupts &= ~(IR_ERR_LEC_30X);
else
- m_can_write(cdev, M_CAN_IE, IR_ALL_INT &
- ~(IR_ERR_LEC_31X));
- else
- m_can_write(cdev, M_CAN_IE, IR_ALL_INT);
+ interrupts &= ~(IR_ERR_LEC_31X);
+ }
+ m_can_write(cdev, M_CAN_IE, interrupts);
/* route all interrupts to INT0 */
m_can_write(cdev, M_CAN_ILS, ILS_ALL_INT0);
--
2.39.2
Interrupts currently get disabled if the interrupt status shows new
received data. Non-peripheral chips handle receiving in a worker thread,
but peripheral chips are handling the receive process in the threaded
interrupt routine itself without scheduling it for a different worker.
So there is no need to disable interrupts for peripheral chips.
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
drivers/net/can/m_can/m_can.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index e7aceeba3759..a5003435802b 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -972,8 +972,8 @@ static int m_can_rx_peripheral(struct net_device *dev, u32 irqstatus)
/* Don't re-enable interrupts if the driver had a fatal error
* (e.g., FIFO read failure).
*/
- if (work_done >= 0)
- m_can_enable_all_interrupts(cdev);
+ if (work_done < 0)
+ m_can_disable_all_interrupts(cdev);
return work_done;
}
@@ -1095,11 +1095,12 @@ static irqreturn_t m_can_isr(int irq, void *dev_id)
*/
if ((ir & IR_RF0N) || (ir & IR_ERR_ALL_30X)) {
cdev->irqstatus = ir;
- m_can_disable_all_interrupts(cdev);
- if (!cdev->is_peripheral)
+ if (!cdev->is_peripheral) {
+ m_can_disable_all_interrupts(cdev);
napi_schedule(&cdev->napi);
- else if (m_can_rx_peripheral(dev, ir) < 0)
+ } else if (m_can_rx_peripheral(dev, ir) < 0) {
goto out_fail;
+ }
}
if (cdev->version == 30) {
--
2.39.2
Interrupts are enabled a few lines further down as well. Remove this
second call to enable all interrupts.
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
drivers/net/can/m_can/m_can.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index 8eb327ae3bdf..5274d9642566 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -1364,7 +1364,6 @@ static int m_can_chip_config(struct net_device *dev)
m_can_write(cdev, M_CAN_TEST, test);
/* Enable interrupts */
- m_can_write(cdev, M_CAN_IR, IR_ALL_INT);
if (!(cdev->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING))
if (cdev->version == 30)
m_can_write(cdev, M_CAN_IE, IR_ALL_INT &
--
2.39.2
The code already exits the function on !ir before this condition. No
need to check again if anything is set as IR_ALL_INT is 0xffffffff.
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
drivers/net/can/m_can/m_can.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index 563625a701fc..8eb327ae3bdf 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -1083,8 +1083,7 @@ static irqreturn_t m_can_isr(int irq, void *dev_id)
return IRQ_NONE;
/* ACK all irqs */
- if (ir & IR_ALL_INT)
- m_can_write(cdev, M_CAN_IR, ir);
+ m_can_write(cdev, M_CAN_IR, ir);
if (cdev->ops->clear_interrupts)
cdev->ops->clear_interrupts(cdev);
--
2.39.2
Combine header and data before writing to the transmit fifo to reduce
the overhead for peripheral chips.
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
drivers/net/can/m_can/m_can.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index a5003435802b..35a2332464e5 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -1681,6 +1681,8 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev)
m_can_write(cdev, M_CAN_TXBAR, 0x1);
/* End of xmit function for version 3.0.x */
} else {
+ char buf[TXB_ELEMENT_SIZE];
+ u8 len_padded = DIV_ROUND_UP(cf->len, 4);
/* Transmit routine for version >= v3.1.x */
txfqs = m_can_read(cdev, M_CAN_TXFQS);
@@ -1720,12 +1722,11 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev)
fifo_header.dlc = FIELD_PREP(TX_BUF_MM_MASK, putidx) |
FIELD_PREP(TX_BUF_DLC_MASK, can_fd_len2dlc(cf->len)) |
fdflags | TX_BUF_EFC;
- err = m_can_fifo_write(cdev, putidx, M_CAN_FIFO_ID, &fifo_header, 2);
- if (err)
- goto out_fail;
+ memcpy(buf, &fifo_header, 8);
+ memcpy_and_pad(&buf[8], len_padded, &cf->data, cf->len, 0);
- err = m_can_fifo_write(cdev, putidx, M_CAN_FIFO_DATA,
- cf->data, DIV_ROUND_UP(cf->len, 4));
+ err = m_can_fifo_write(cdev, putidx, M_CAN_FIFO_ID,
+ buf, 2 + len_padded);
if (err)
goto out_fail;
--
2.39.2
m_can offers the possibility to set an interrupt on reaching a watermark
level in the receive FIFO. This can be used to implement coalescing.
Unfortunately there is no hardware timeout available to trigger an
interrupt if only a few messages were received within a given time. To
solve this I am using a hrtimer to wake up the irq thread after x
microseconds.
The timer is always started if receive coalescing is enabled and new
received frames were available during an interrupt. The timer is stopped
if during a interrupt handling no new data was available.
If the timer is started the new item interrupt is disabled and the
watermark interrupt takes over. If the timer is not started again, the
new item interrupt is enabled again, notifying the handler about every
new item received.
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
drivers/net/can/m_can/m_can.c | 69 ++++++++++++++++++++++++++++++++---
drivers/net/can/m_can/m_can.h | 7 ++++
2 files changed, 70 insertions(+), 6 deletions(-)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index 35a2332464e5..e7dc083e32e4 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -1070,15 +1070,55 @@ static int m_can_echo_tx_event(struct net_device *dev)
return err;
}
+static void m_can_interrupt_enable(struct m_can_classdev *cdev, u32 interrupts)
+{
+ if (cdev->active_interrupts == interrupts)
+ return;
+ cdev->ops->write_reg(cdev, M_CAN_IE, interrupts);
+ cdev->active_interrupts = interrupts;
+}
+
+static void m_can_coalescing_disable(struct m_can_classdev *cdev)
+{
+ u32 new_interrupts = cdev->active_interrupts | IR_RF0N;
+
+ hrtimer_cancel(&cdev->irq_timer);
+ m_can_interrupt_enable(cdev, new_interrupts);
+}
+
+static void m_can_coalescing_update(struct m_can_classdev *cdev, u32 ir)
+{
+ u32 new_interrupts = cdev->active_interrupts;
+ bool enable_timer = false;
+
+ if (cdev->rx_coalesce_usecs_irq > 0 && (ir & (IR_RF0N | IR_RF0W))) {
+ enable_timer = true;
+ new_interrupts &= ~IR_RF0N;
+ } else if (!hrtimer_active(&cdev->irq_timer)) {
+ new_interrupts |= IR_RF0N;
+ }
+
+ m_can_interrupt_enable(cdev, new_interrupts);
+ if (enable_timer) {
+ hrtimer_start(&cdev->irq_timer,
+ ns_to_ktime(cdev->rx_coalesce_usecs_irq * NSEC_PER_USEC),
+ HRTIMER_MODE_REL);
+ }
+}
+
static irqreturn_t m_can_isr(int irq, void *dev_id)
{
struct net_device *dev = (struct net_device *)dev_id;
struct m_can_classdev *cdev = netdev_priv(dev);
u32 ir;
- if (pm_runtime_suspended(cdev->dev))
+ if (pm_runtime_suspended(cdev->dev)) {
+ m_can_coalescing_disable(cdev);
return IRQ_NONE;
+ }
+
ir = m_can_read(cdev, M_CAN_IR);
+ m_can_coalescing_update(cdev, ir);
if (!ir)
return IRQ_NONE;
@@ -1093,13 +1133,17 @@ static irqreturn_t m_can_isr(int irq, void *dev_id)
* - state change IRQ
* - bus error IRQ and bus error reporting
*/
- if ((ir & IR_RF0N) || (ir & IR_ERR_ALL_30X)) {
+ if (ir & (IR_RF0N | IR_RF0W | IR_ERR_ALL_30X)) {
cdev->irqstatus = ir;
if (!cdev->is_peripheral) {
m_can_disable_all_interrupts(cdev);
napi_schedule(&cdev->napi);
- } else if (m_can_rx_peripheral(dev, ir) < 0) {
- goto out_fail;
+ } else {
+ int pkts;
+
+ pkts = m_can_rx_peripheral(dev, ir);
+ if (pkts < 0)
+ goto out_fail;
}
}
@@ -1135,6 +1179,15 @@ static irqreturn_t m_can_isr(int irq, void *dev_id)
return IRQ_HANDLED;
}
+static enum hrtimer_restart m_can_irq_timer(struct hrtimer *timer)
+{
+ struct m_can_classdev *cdev = container_of(timer, struct m_can_classdev, irq_timer);
+
+ irq_wake_thread(cdev->net->irq, cdev->net);
+
+ return HRTIMER_NORESTART;
+}
+
static const struct can_bittiming_const m_can_bittiming_const_30X = {
.name = KBUILD_MODNAME,
.tseg1_min = 2, /* Time segment 1 = prop_seg + phase_seg1 */
@@ -1275,7 +1328,7 @@ static int m_can_chip_config(struct net_device *dev)
/* Disable unused interrupts */
interrupts &= ~(IR_ARA | IR_ELO | IR_DRX | IR_TEFF | IR_TEFW | IR_TFE |
IR_TCF | IR_HPM | IR_RF1F | IR_RF1W | IR_RF1N |
- IR_RF0F | IR_RF0W);
+ IR_RF0F);
m_can_config_endisable(cdev, true);
@@ -1319,6 +1372,7 @@ static int m_can_chip_config(struct net_device *dev)
/* rx fifo configuration, blocking mode, fifo size 1 */
m_can_write(cdev, M_CAN_RXF0C,
+ FIELD_PREP(RXFC_FWM_MASK, cdev->rx_max_coalesced_frames_irq) |
FIELD_PREP(RXFC_FS_MASK, cdev->mcfg[MRAM_RXF0].num) |
cdev->mcfg[MRAM_RXF0].off);
@@ -1377,7 +1431,7 @@ static int m_can_chip_config(struct net_device *dev)
else
interrupts &= ~(IR_ERR_LEC_31X);
}
- m_can_write(cdev, M_CAN_IE, interrupts);
+ m_can_interrupt_enable(cdev, interrupts);
/* route all interrupts to INT0 */
m_can_write(cdev, M_CAN_ILS, ILS_ALL_INT0);
@@ -2041,6 +2095,9 @@ int m_can_class_register(struct m_can_classdev *cdev)
of_can_transceiver(cdev->net);
+ hrtimer_init(&cdev->irq_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ cdev->irq_timer.function = m_can_irq_timer;
+
dev_info(cdev->dev, "%s device registered (irq=%d, version=%d)\n",
KBUILD_MODNAME, cdev->net->irq, cdev->version);
diff --git a/drivers/net/can/m_can/m_can.h b/drivers/net/can/m_can/m_can.h
index a839dc71dc9b..c59099d3f5b9 100644
--- a/drivers/net/can/m_can/m_can.h
+++ b/drivers/net/can/m_can/m_can.h
@@ -84,6 +84,8 @@ struct m_can_classdev {
struct sk_buff *tx_skb;
struct phy *transceiver;
+ struct hrtimer irq_timer;
+
struct m_can_ops *ops;
int version;
@@ -92,6 +94,11 @@ struct m_can_classdev {
int pm_clock_support;
int is_peripheral;
+ // Cached M_CAN_IE register content
+ u32 active_interrupts;
+ u32 rx_max_coalesced_frames_irq;
+ u32 rx_coalesce_usecs_irq;
+
struct mram_cfg mcfg[MRAM_CFG_NUM];
};
--
2.39.2
Add the possibility to set coalescing parameters with ethtool.
rx-frames-irq and rx-usecs-irq can only be set and unset together as the
implemented mechanism would not work otherwise. rx-frames-irq can't be
greater than the RX FIFO size.
Also all values can only be changed if the chip is not active.
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
drivers/net/can/m_can/m_can.c | 46 +++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index 94c962ac6992..7f8decfae81e 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -1936,8 +1936,54 @@ static const struct net_device_ops m_can_netdev_ops = {
.ndo_change_mtu = can_change_mtu,
};
+static int m_can_get_coalesce(struct net_device *dev,
+ struct ethtool_coalesce *ec,
+ struct kernel_ethtool_coalesce *kec,
+ struct netlink_ext_ack *ext_ack)
+{
+ struct m_can_classdev *cdev = netdev_priv(dev);
+
+ ec->rx_max_coalesced_frames_irq = cdev->rx_max_coalesced_frames_irq;
+ ec->rx_coalesce_usecs_irq = cdev->rx_coalesce_usecs_irq;
+
+ return 0;
+}
+
+static int m_can_set_coalesce(struct net_device *dev,
+ struct ethtool_coalesce *ec,
+ struct kernel_ethtool_coalesce *kec,
+ struct netlink_ext_ack *ext_ack)
+{
+ struct m_can_classdev *cdev = netdev_priv(dev);
+
+ if (cdev->can.state != CAN_STATE_STOPPED) {
+ netdev_err(dev, "Device is in use, please shut it down first\n");
+ return -EBUSY;
+ }
+
+ if (ec->rx_max_coalesced_frames_irq > cdev->mcfg[MRAM_RXF0].num) {
+ netdev_err(dev, "rx-frames-irq %u greater than the RX FIFO %u\n",
+ ec->rx_max_coalesced_frames_irq,
+ cdev->mcfg[MRAM_RXF0].num);
+ return -EINVAL;
+ }
+ if ((ec->rx_max_coalesced_frames_irq == 0) != (ec->rx_coalesce_usecs_irq == 0)) {
+ netdev_err(dev, "rx-frames-irq and rx-usecs-irq can only be set together\n");
+ return -EINVAL;
+ }
+
+ cdev->rx_max_coalesced_frames_irq = ec->rx_max_coalesced_frames_irq;
+ cdev->rx_coalesce_usecs_irq = ec->rx_coalesce_usecs_irq;
+
+ return 0;
+}
+
static const struct ethtool_ops m_can_ethtool_ops = {
+ .supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS_IRQ |
+ ETHTOOL_COALESCE_RX_MAX_FRAMES_IRQ,
.get_ts_info = ethtool_op_get_ts_info,
+ .get_coalesce = m_can_get_coalesce,
+ .set_coalesce = m_can_set_coalesce,
};
static int register_m_can_dev(struct net_device *dev)
--
2.39.2
Add get/set functions for ethtool coalescing. tx-frames-irq and
tx-usecs-irq can only be set/unset together. tx-frames-irq needs to be
less than TXE and TXB.
As rx and tx share the same timer, rx-usecs-irq and tx-usecs-irq can be
enabled/disabled individually but they need to have the same value if
enabled.
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
drivers/net/can/m_can/m_can.c | 38 ++++++++++++++++++++++++++++++++++-
1 file changed, 37 insertions(+), 1 deletion(-)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index 7f8decfae81e..4e794166664a 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -1945,6 +1945,8 @@ static int m_can_get_coalesce(struct net_device *dev,
ec->rx_max_coalesced_frames_irq = cdev->rx_max_coalesced_frames_irq;
ec->rx_coalesce_usecs_irq = cdev->rx_coalesce_usecs_irq;
+ ec->tx_max_coalesced_frames_irq = cdev->tx_max_coalesced_frames_irq;
+ ec->tx_coalesce_usecs_irq = cdev->tx_coalesce_usecs_irq;
return 0;
}
@@ -1971,16 +1973,50 @@ static int m_can_set_coalesce(struct net_device *dev,
netdev_err(dev, "rx-frames-irq and rx-usecs-irq can only be set together\n");
return -EINVAL;
}
+ if (ec->tx_max_coalesced_frames_irq > cdev->mcfg[MRAM_TXE].num) {
+ netdev_err(dev, "tx-frames-irq %u greater than the TX event FIFO %u\n",
+ ec->tx_max_coalesced_frames_irq,
+ cdev->mcfg[MRAM_TXE].num);
+ return -EINVAL;
+ }
+ if (ec->tx_max_coalesced_frames_irq > cdev->mcfg[MRAM_TXB].num) {
+ netdev_err(dev, "tx-frames-irq %u greater than the TX FIFO %u\n",
+ ec->tx_max_coalesced_frames_irq,
+ cdev->mcfg[MRAM_TXB].num);
+ return -EINVAL;
+ }
+ if ((ec->tx_max_coalesced_frames_irq == 0) != (ec->tx_coalesce_usecs_irq == 0)) {
+ netdev_err(dev, "tx-frames-irq and tx-usecs-irq can only be set together\n");
+ return -EINVAL;
+ }
+ if (ec->rx_coalesce_usecs_irq != 0 && ec->tx_coalesce_usecs_irq != 0 &&
+ ec->rx_coalesce_usecs_irq != ec->tx_coalesce_usecs_irq) {
+ netdev_err(dev, "rx-usecs-irq %u needs to be equal to tx-usecs-irq %u if both are enabled\n",
+ ec->rx_coalesce_usecs_irq,
+ ec->tx_coalesce_usecs_irq);
+ return -EINVAL;
+ }
cdev->rx_max_coalesced_frames_irq = ec->rx_max_coalesced_frames_irq;
cdev->rx_coalesce_usecs_irq = ec->rx_coalesce_usecs_irq;
+ cdev->tx_max_coalesced_frames_irq = ec->tx_max_coalesced_frames_irq;
+ cdev->tx_coalesce_usecs_irq = ec->tx_coalesce_usecs_irq;
+
+ if (cdev->rx_coalesce_usecs_irq)
+ cdev->irq_timer_wait =
+ ns_to_ktime(cdev->rx_coalesce_usecs_irq * NSEC_PER_USEC);
+ else
+ cdev->irq_timer_wait =
+ ns_to_ktime(cdev->tx_coalesce_usecs_irq * NSEC_PER_USEC);
return 0;
}
static const struct ethtool_ops m_can_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS_IRQ |
- ETHTOOL_COALESCE_RX_MAX_FRAMES_IRQ,
+ ETHTOOL_COALESCE_RX_MAX_FRAMES_IRQ |
+ ETHTOOL_COALESCE_TX_USECS_IRQ |
+ ETHTOOL_COALESCE_TX_MAX_FRAMES_IRQ,
.get_ts_info = ethtool_op_get_ts_info,
.get_coalesce = m_can_get_coalesce,
.set_coalesce = m_can_set_coalesce,
--
2.39.2
Keep track of the number of transmits in flight.
This patch prepares the driver to control the network interface queue
based on this counter. By itself this counter be
implemented with an atomic, but as we need to do other things in the
critical sections later I am using a spinlock instead.
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
drivers/net/can/m_can/m_can.c | 41 ++++++++++++++++++++++++++++++++++-
drivers/net/can/m_can/m_can.h | 4 ++++
2 files changed, 44 insertions(+), 1 deletion(-)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index 27d36bcc094c..4ad8f08f8284 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -442,6 +442,7 @@ static u32 m_can_get_timestamp(struct m_can_classdev *cdev)
static void m_can_clean(struct net_device *net)
{
struct m_can_classdev *cdev = netdev_priv(net);
+ unsigned long irqflags;
for (int i = 0; i != cdev->tx_fifo_size; ++i) {
if (!cdev->tx_ops[i].skb)
@@ -453,6 +454,10 @@ static void m_can_clean(struct net_device *net)
for (int i = 0; i != cdev->can.echo_skb_max; ++i)
can_free_echo_skb(cdev->net, i, NULL);
+
+ spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
+ cdev->tx_fifo_in_flight = 0;
+ spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
}
/* For peripherals, pass skb to rx-offload, which will push skb from
@@ -1023,6 +1028,24 @@ static void m_can_tx_update_stats(struct m_can_classdev *cdev,
stats->tx_packets++;
}
+static void m_can_finish_tx(struct m_can_classdev *cdev, int transmitted)
+{
+ unsigned long irqflags;
+
+ spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
+ cdev->tx_fifo_in_flight -= transmitted;
+ spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
+}
+
+static void m_can_start_tx(struct m_can_classdev *cdev)
+{
+ unsigned long irqflags;
+
+ spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
+ ++cdev->tx_fifo_in_flight;
+ spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
+}
+
static int m_can_echo_tx_event(struct net_device *dev)
{
u32 txe_count = 0;
@@ -1032,6 +1055,7 @@ static int m_can_echo_tx_event(struct net_device *dev)
int i = 0;
int err = 0;
unsigned int msg_mark;
+ int processed = 0;
struct m_can_classdev *cdev = netdev_priv(dev);
@@ -1061,12 +1085,15 @@ static int m_can_echo_tx_event(struct net_device *dev)
/* update stats */
m_can_tx_update_stats(cdev, msg_mark, timestamp);
+ ++processed;
}
if (ack_fgi != -1)
m_can_write(cdev, M_CAN_TXEFA, FIELD_PREP(TXEFA_EFAI_MASK,
ack_fgi));
+ m_can_finish_tx(cdev, processed);
+
return err;
}
@@ -1161,6 +1188,7 @@ static irqreturn_t m_can_isr(int irq, void *dev_id)
timestamp = m_can_get_timestamp(cdev);
m_can_tx_update_stats(cdev, 0, timestamp);
netif_wake_queue(dev);
+ m_can_finish_tx(cdev, 1);
}
} else {
if (ir & (IR_TEFN | IR_TEFW)) {
@@ -1846,11 +1874,22 @@ static netdev_tx_t m_can_start_peripheral_xmit(struct m_can_classdev *cdev,
}
netif_stop_queue(cdev->net);
+
+ m_can_start_tx(cdev);
+
m_can_tx_queue_skb(cdev, skb);
return NETDEV_TX_OK;
}
+static netdev_tx_t m_can_start_fast_xmit(struct m_can_classdev *cdev,
+ struct sk_buff *skb)
+{
+ m_can_start_tx(cdev);
+
+ return m_can_tx_handler(cdev, skb);
+}
+
static netdev_tx_t m_can_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
@@ -1862,7 +1901,7 @@ static netdev_tx_t m_can_start_xmit(struct sk_buff *skb,
if (cdev->is_peripheral)
return m_can_start_peripheral_xmit(cdev, skb);
else
- return m_can_tx_handler(cdev, skb);
+ return m_can_start_fast_xmit(cdev, skb);
}
static int m_can_open(struct net_device *dev)
diff --git a/drivers/net/can/m_can/m_can.h b/drivers/net/can/m_can/m_can.h
index 2e1a52980a18..e230cf320a6c 100644
--- a/drivers/net/can/m_can/m_can.h
+++ b/drivers/net/can/m_can/m_can.h
@@ -109,6 +109,10 @@ struct m_can_classdev {
// Store this internally to avoid fetch delays on peripheral chips
int tx_fifo_putidx;
+ /* Protects shared state between start_xmit and m_can_isr */
+ spinlock_t tx_handling_spinlock;
+ int tx_fifo_in_flight;
+
struct m_can_tx_op *tx_ops;
int tx_fifo_size;
int next_tx_op;
--
2.39.2
The current implementation uses the workqueue for peripheral chips to
submit work. Only a single work item is queued and used at any time.
To be able to keep more than one transmit in flight at a time, prepare
the workqueue to support multiple transmits at the same time.
Each work item now has a separate storage for a skb and a pointer to
cdev. This assures that each workitem can be processed individually.
The workqueue is replaced by an ordered workqueue which makes sure that
only a single worker processes the items queued on the workqueue. Also
items are ordered by the order they were enqueued. This removes most of
the concurrency the workqueue normally offers. It is not necessary for
this driver.
The cleanup functions have to be adopted a bit to handle this new
mechanism.
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
drivers/net/can/m_can/m_can.c | 109 ++++++++++++++++++++--------------
drivers/net/can/m_can/m_can.h | 12 +++-
2 files changed, 74 insertions(+), 47 deletions(-)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index d5bcce948d2c..27d36bcc094c 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -443,17 +443,16 @@ static void m_can_clean(struct net_device *net)
{
struct m_can_classdev *cdev = netdev_priv(net);
- if (cdev->tx_skb) {
- int putidx = 0;
+ for (int i = 0; i != cdev->tx_fifo_size; ++i) {
+ if (!cdev->tx_ops[i].skb)
+ continue;
net->stats.tx_errors++;
- if (cdev->version > 30)
- putidx = FIELD_GET(TXFQS_TFQPI_MASK,
- m_can_read(cdev, M_CAN_TXFQS));
-
- can_free_echo_skb(cdev->net, putidx, NULL);
- cdev->tx_skb = NULL;
+ cdev->tx_ops[i].skb = NULL;
}
+
+ for (int i = 0; i != cdev->can.echo_skb_max; ++i)
+ can_free_echo_skb(cdev->net, i, NULL);
}
/* For peripherals, pass skb to rx-offload, which will push skb from
@@ -1656,8 +1655,9 @@ static int m_can_close(struct net_device *dev)
m_can_clk_stop(cdev);
free_irq(dev->irq, dev);
+ m_can_clean(dev);
+
if (cdev->is_peripheral) {
- cdev->tx_skb = NULL;
destroy_workqueue(cdev->tx_wq);
cdev->tx_wq = NULL;
can_rx_offload_disable(&cdev->offload);
@@ -1684,19 +1684,17 @@ static int m_can_next_echo_skb_occupied(struct net_device *dev, int putidx)
return !!cdev->can.echo_skb[next_idx];
}
-static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev)
+static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev,
+ struct sk_buff *skb)
{
- struct canfd_frame *cf = (struct canfd_frame *)cdev->tx_skb->data;
+ struct canfd_frame *cf = (struct canfd_frame *)skb->data;
struct net_device *dev = cdev->net;
- struct sk_buff *skb = cdev->tx_skb;
struct id_and_dlc fifo_header;
u32 cccr, fdflags;
u32 txfqs;
int err;
int putidx;
- cdev->tx_skb = NULL;
-
/* Generate ID field for TX buffer Element */
/* Common to all supported M_CAN versions */
if (cf->can_id & CAN_EFF_FLAG) {
@@ -1821,10 +1819,36 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev)
static void m_can_tx_work_queue(struct work_struct *ws)
{
- struct m_can_classdev *cdev = container_of(ws, struct m_can_classdev,
- tx_work);
+ struct m_can_tx_op *op = container_of(ws, struct m_can_tx_op, work);
+ struct m_can_classdev *cdev = op->cdev;
+ struct sk_buff *skb = op->skb;
- m_can_tx_handler(cdev);
+ op->skb = NULL;
+ m_can_tx_handler(cdev, skb);
+}
+
+static void m_can_tx_queue_skb(struct m_can_classdev *cdev, struct sk_buff *skb)
+{
+ cdev->tx_ops[cdev->next_tx_op].skb = skb;
+ queue_work(cdev->tx_wq, &cdev->tx_ops[cdev->next_tx_op].work);
+
+ ++cdev->next_tx_op;
+ if (cdev->next_tx_op >= cdev->tx_fifo_size)
+ cdev->next_tx_op = 0;
+}
+
+static netdev_tx_t m_can_start_peripheral_xmit(struct m_can_classdev *cdev,
+ struct sk_buff *skb)
+{
+ if (cdev->can.state == CAN_STATE_BUS_OFF) {
+ m_can_clean(cdev->net);
+ return NETDEV_TX_OK;
+ }
+
+ netif_stop_queue(cdev->net);
+ m_can_tx_queue_skb(cdev, skb);
+
+ return NETDEV_TX_OK;
}
static netdev_tx_t m_can_start_xmit(struct sk_buff *skb,
@@ -1835,30 +1859,10 @@ static netdev_tx_t m_can_start_xmit(struct sk_buff *skb,
if (can_dev_dropped_skb(dev, skb))
return NETDEV_TX_OK;
- if (cdev->is_peripheral) {
- if (cdev->tx_skb) {
- netdev_err(dev, "hard_xmit called while tx busy\n");
- return NETDEV_TX_BUSY;
- }
-
- if (cdev->can.state == CAN_STATE_BUS_OFF) {
- m_can_clean(dev);
- } else {
- /* Need to stop the queue to avoid numerous requests
- * from being sent. Suggested improvement is to create
- * a queueing mechanism that will queue the skbs and
- * process them in order.
- */
- cdev->tx_skb = skb;
- netif_stop_queue(cdev->net);
- queue_work(cdev->tx_wq, &cdev->tx_work);
- }
- } else {
- cdev->tx_skb = skb;
- return m_can_tx_handler(cdev);
- }
-
- return NETDEV_TX_OK;
+ if (cdev->is_peripheral)
+ return m_can_start_peripheral_xmit(cdev, skb);
+ else
+ return m_can_tx_handler(cdev, skb);
}
static int m_can_open(struct net_device *dev)
@@ -1886,15 +1890,17 @@ static int m_can_open(struct net_device *dev)
/* register interrupt handler */
if (cdev->is_peripheral) {
- cdev->tx_skb = NULL;
- cdev->tx_wq = alloc_workqueue("mcan_wq",
- WQ_FREEZABLE | WQ_MEM_RECLAIM, 0);
+ cdev->tx_wq = alloc_ordered_workqueue("mcan_wq",
+ WQ_FREEZABLE | WQ_MEM_RECLAIM);
if (!cdev->tx_wq) {
err = -ENOMEM;
goto out_wq_fail;
}
- INIT_WORK(&cdev->tx_work, m_can_tx_work_queue);
+ for (int i = 0; i != cdev->tx_fifo_size; ++i) {
+ cdev->tx_ops[i].cdev = cdev;
+ INIT_WORK(&cdev->tx_ops[i].work, m_can_tx_work_queue);
+ }
err = request_threaded_irq(dev->irq, NULL, m_can_isr,
IRQF_ONESHOT,
@@ -2164,6 +2170,19 @@ int m_can_class_register(struct m_can_classdev *cdev)
{
int ret;
+ cdev->tx_fifo_size = max(1, min(cdev->mcfg[MRAM_TXB].num,
+ cdev->mcfg[MRAM_TXE].num));
+ if (cdev->is_peripheral) {
+ cdev->tx_ops =
+ devm_kzalloc(cdev->dev,
+ cdev->tx_fifo_size * sizeof(*cdev->tx_ops),
+ GFP_KERNEL);
+ if (!cdev->tx_ops) {
+ dev_err(cdev->dev, "Failed to allocate tx_ops for workqueue\n");
+ return -ENOMEM;
+ }
+ }
+
if (cdev->pm_clock_support) {
ret = m_can_clk_start(cdev);
if (ret)
diff --git a/drivers/net/can/m_can/m_can.h b/drivers/net/can/m_can/m_can.h
index 548ae908ac4e..2e1a52980a18 100644
--- a/drivers/net/can/m_can/m_can.h
+++ b/drivers/net/can/m_can/m_can.h
@@ -70,6 +70,12 @@ struct m_can_ops {
int (*init)(struct m_can_classdev *cdev);
};
+struct m_can_tx_op {
+ struct m_can_classdev *cdev;
+ struct work_struct work;
+ struct sk_buff *skb;
+};
+
struct m_can_classdev {
struct can_priv can;
struct can_rx_offload offload;
@@ -80,8 +86,6 @@ struct m_can_classdev {
struct clk *cclk;
struct workqueue_struct *tx_wq;
- struct work_struct tx_work;
- struct sk_buff *tx_skb;
struct phy *transceiver;
struct hrtimer irq_timer;
@@ -105,6 +109,10 @@ struct m_can_classdev {
// Store this internally to avoid fetch delays on peripheral chips
int tx_fifo_putidx;
+ struct m_can_tx_op *tx_ops;
+ int tx_fifo_size;
+ int next_tx_op;
+
struct mram_cfg mcfg[MRAM_CFG_NUM];
};
--
2.39.2
m_can_tx_handler is the only place where data is written to the tx fifo.
We can calculate the putidx in the driver code here to avoid the
dependency on the txfqs register.
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
drivers/net/can/m_can/m_can.c | 8 +++++++-
drivers/net/can/m_can/m_can.h | 3 +++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index 4e794166664a..d5bcce948d2c 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -1475,6 +1475,10 @@ static int m_can_start(struct net_device *dev)
m_can_enable_all_interrupts(cdev);
+ if (cdev->version > 30)
+ cdev->tx_fifo_putidx = FIELD_GET(TXFQS_TFQPI_MASK,
+ m_can_read(cdev, M_CAN_TXFQS));
+
return 0;
}
@@ -1765,7 +1769,7 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev)
}
/* get put index for frame */
- putidx = FIELD_GET(TXFQS_TFQPI_MASK, txfqs);
+ putidx = cdev->tx_fifo_putidx;
/* Construct DLC Field, with CAN-FD configuration.
* Use the put index of the fifo as the message marker,
@@ -1798,6 +1802,8 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev)
/* Enable TX FIFO element to start transfer */
m_can_write(cdev, M_CAN_TXBAR, (1 << putidx));
+ cdev->tx_fifo_putidx = (++cdev->tx_fifo_putidx >= cdev->can.echo_skb_max ?
+ 0 : cdev->tx_fifo_putidx);
/* stop network queue if fifo full */
if (m_can_tx_fifo_full(cdev) ||
diff --git a/drivers/net/can/m_can/m_can.h b/drivers/net/can/m_can/m_can.h
index d0c21eddb6ec..548ae908ac4e 100644
--- a/drivers/net/can/m_can/m_can.h
+++ b/drivers/net/can/m_can/m_can.h
@@ -102,6 +102,9 @@ struct m_can_classdev {
u32 tx_max_coalesced_frames_irq;
u32 tx_coalesce_usecs_irq;
+ // Store this internally to avoid fetch delays on peripheral chips
+ int tx_fifo_putidx;
+
struct mram_cfg mcfg[MRAM_CFG_NUM];
};
--
2.39.2
Extend the coalescing implementation for transmits.
In normal mode the chip raises an interrupt for every finished transmit.
This implementation switches to coalescing mode as soon as an interrupt
handled a transmit. For coalescing the watermark level interrupt is used
to interrupt exactly after x frames were sent. It switches back into
normal mode once there was an interrupt with no finished transmit and
the timer being inactive.
The timer is shared with receive coalescing. The time for receive and
transmit coalescing timers have to be the same for that to work. The
benefit is to have only a single running timer.
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
drivers/net/can/m_can/m_can.c | 33 ++++++++++++++++++++-------------
drivers/net/can/m_can/m_can.h | 3 +++
2 files changed, 23 insertions(+), 13 deletions(-)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index e7dc083e32e4..94c962ac6992 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -255,6 +255,7 @@ enum m_can_reg {
#define TXESC_TBDS_64B 0x7
/* Tx Event FIFO Configuration (TXEFC) */
+#define TXEFC_EFWM_MASK GENMASK(29, 24)
#define TXEFC_EFS_MASK GENMASK(21, 16)
/* Tx Event FIFO Status (TXEFS) */
@@ -1080,7 +1081,7 @@ static void m_can_interrupt_enable(struct m_can_classdev *cdev, u32 interrupts)
static void m_can_coalescing_disable(struct m_can_classdev *cdev)
{
- u32 new_interrupts = cdev->active_interrupts | IR_RF0N;
+ u32 new_interrupts = cdev->active_interrupts | IR_RF0N | IR_TEFN;
hrtimer_cancel(&cdev->irq_timer);
m_can_interrupt_enable(cdev, new_interrupts);
@@ -1089,21 +1090,26 @@ static void m_can_coalescing_disable(struct m_can_classdev *cdev)
static void m_can_coalescing_update(struct m_can_classdev *cdev, u32 ir)
{
u32 new_interrupts = cdev->active_interrupts;
- bool enable_timer = false;
+ bool enable_rx_timer = false;
+ bool enable_tx_timer = false;
if (cdev->rx_coalesce_usecs_irq > 0 && (ir & (IR_RF0N | IR_RF0W))) {
- enable_timer = true;
+ enable_rx_timer = true;
new_interrupts &= ~IR_RF0N;
- } else if (!hrtimer_active(&cdev->irq_timer)) {
- new_interrupts |= IR_RF0N;
}
+ if (cdev->tx_coalesce_usecs_irq > 0 && (ir & (IR_TEFN | IR_TEFW))) {
+ enable_tx_timer = true;
+ new_interrupts &= ~IR_TEFN;
+ }
+ if (!enable_rx_timer && !hrtimer_active(&cdev->irq_timer))
+ new_interrupts |= IR_RF0N;
+ if (!enable_tx_timer && !hrtimer_active(&cdev->irq_timer))
+ new_interrupts |= IR_TEFN;
m_can_interrupt_enable(cdev, new_interrupts);
- if (enable_timer) {
- hrtimer_start(&cdev->irq_timer,
- ns_to_ktime(cdev->rx_coalesce_usecs_irq * NSEC_PER_USEC),
+ if (enable_rx_timer | enable_tx_timer)
+ hrtimer_start(&cdev->irq_timer, cdev->irq_timer_wait,
HRTIMER_MODE_REL);
- }
}
static irqreturn_t m_can_isr(int irq, void *dev_id)
@@ -1158,7 +1164,7 @@ static irqreturn_t m_can_isr(int irq, void *dev_id)
netif_wake_queue(dev);
}
} else {
- if (ir & IR_TEFN) {
+ if (ir & (IR_TEFN | IR_TEFW)) {
/* New TX FIFO Element arrived */
if (m_can_echo_tx_event(dev) != 0)
goto out_fail;
@@ -1326,9 +1332,8 @@ static int m_can_chip_config(struct net_device *dev)
}
/* Disable unused interrupts */
- interrupts &= ~(IR_ARA | IR_ELO | IR_DRX | IR_TEFF | IR_TEFW | IR_TFE |
- IR_TCF | IR_HPM | IR_RF1F | IR_RF1W | IR_RF1N |
- IR_RF0F);
+ interrupts &= ~(IR_ARA | IR_ELO | IR_DRX | IR_TEFF | IR_TFE | IR_TCF |
+ IR_HPM | IR_RF1F | IR_RF1W | IR_RF1N | IR_RF0F);
m_can_config_endisable(cdev, true);
@@ -1365,6 +1370,8 @@ static int m_can_chip_config(struct net_device *dev)
} else {
/* Full TX Event FIFO is used */
m_can_write(cdev, M_CAN_TXEFC,
+ FIELD_PREP(TXEFC_EFWM_MASK,
+ cdev->tx_max_coalesced_frames_irq) |
FIELD_PREP(TXEFC_EFS_MASK,
cdev->mcfg[MRAM_TXE].num) |
cdev->mcfg[MRAM_TXE].off);
diff --git a/drivers/net/can/m_can/m_can.h b/drivers/net/can/m_can/m_can.h
index c59099d3f5b9..d0c21eddb6ec 100644
--- a/drivers/net/can/m_can/m_can.h
+++ b/drivers/net/can/m_can/m_can.h
@@ -85,6 +85,7 @@ struct m_can_classdev {
struct phy *transceiver;
struct hrtimer irq_timer;
+ ktime_t irq_timer_wait;
struct m_can_ops *ops;
@@ -98,6 +99,8 @@ struct m_can_classdev {
u32 active_interrupts;
u32 rx_max_coalesced_frames_irq;
u32 rx_coalesce_usecs_irq;
+ u32 tx_max_coalesced_frames_irq;
+ u32 tx_coalesce_usecs_irq;
struct mram_cfg mcfg[MRAM_CFG_NUM];
};
--
2.39.2
The network queue is currently always stopped in start_xmit and
continued in the interrupt handler. This is not possible anymore if we
want to keep multiple transmits in flight in parallel.
Use the previously introduced tx_fifo_in_flight counter to control the
network queue instead. This has the benefit of not needing to ask the
hardware about fifo status.
This patch stops the network queue in start_xmit if the number of
transmits in flight reaches the size of the fifo and wakes up the queue
from the interrupt handler once the transmits in flight drops below the
fifo size. This means any skbs over the limit will be rejected
immediately in start_xmit (it shouldn't be possible at all to reach that
state anyways).
The maximum number of transmits in flight is the size of the fifo.
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
drivers/net/can/m_can/m_can.c | 71 +++++++++++++----------------------
1 file changed, 26 insertions(+), 45 deletions(-)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index 4ad8f08f8284..3cb3d01e1a61 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -370,16 +370,6 @@ m_can_txe_fifo_read(struct m_can_classdev *cdev, u32 fgi, u32 offset, u32 *val)
return cdev->ops->read_fifo(cdev, addr_offset, val, 1);
}
-static inline bool _m_can_tx_fifo_full(u32 txfqs)
-{
- return !!(txfqs & TXFQS_TFQF);
-}
-
-static inline bool m_can_tx_fifo_full(struct m_can_classdev *cdev)
-{
- return _m_can_tx_fifo_full(m_can_read(cdev, M_CAN_TXFQS));
-}
-
static void m_can_config_endisable(struct m_can_classdev *cdev, bool enable)
{
u32 cccr = m_can_read(cdev, M_CAN_CCCR);
@@ -1033,17 +1023,31 @@ static void m_can_finish_tx(struct m_can_classdev *cdev, int transmitted)
unsigned long irqflags;
spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
+ if (cdev->tx_fifo_in_flight >= cdev->tx_fifo_size && transmitted > 0)
+ netif_wake_queue(cdev->net);
cdev->tx_fifo_in_flight -= transmitted;
spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
}
-static void m_can_start_tx(struct m_can_classdev *cdev)
+static netdev_tx_t m_can_start_tx(struct m_can_classdev *cdev)
{
unsigned long irqflags;
+ int tx_fifo_in_flight;
spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
- ++cdev->tx_fifo_in_flight;
+ tx_fifo_in_flight = cdev->tx_fifo_in_flight + 1;
+ if (tx_fifo_in_flight >= cdev->tx_fifo_size) {
+ netif_stop_queue(cdev->net);
+ if (tx_fifo_in_flight > cdev->tx_fifo_size) {
+ netdev_err(cdev->net, "hard_xmit called while TX FIFO full\n");
+ spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
+ return NETDEV_TX_BUSY;
+ }
+ }
+ cdev->tx_fifo_in_flight = tx_fifo_in_flight;
spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
+
+ return NETDEV_TX_OK;
}
static int m_can_echo_tx_event(struct net_device *dev)
@@ -1187,7 +1191,6 @@ static irqreturn_t m_can_isr(int irq, void *dev_id)
if (cdev->is_peripheral)
timestamp = m_can_get_timestamp(cdev);
m_can_tx_update_stats(cdev, 0, timestamp);
- netif_wake_queue(dev);
m_can_finish_tx(cdev, 1);
}
} else {
@@ -1195,10 +1198,6 @@ static irqreturn_t m_can_isr(int irq, void *dev_id)
/* New TX FIFO Element arrived */
if (m_can_echo_tx_event(dev) != 0)
goto out_fail;
-
- if (netif_queue_stopped(dev) &&
- !m_can_tx_fifo_full(cdev))
- netif_wake_queue(dev);
}
}
@@ -1719,7 +1718,6 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev,
struct net_device *dev = cdev->net;
struct id_and_dlc fifo_header;
u32 cccr, fdflags;
- u32 txfqs;
int err;
int putidx;
@@ -1776,24 +1774,6 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev,
u8 len_padded = DIV_ROUND_UP(cf->len, 4);
/* Transmit routine for version >= v3.1.x */
- txfqs = m_can_read(cdev, M_CAN_TXFQS);
-
- /* Check if FIFO full */
- if (_m_can_tx_fifo_full(txfqs)) {
- /* This shouldn't happen */
- netif_stop_queue(dev);
- netdev_warn(dev,
- "TX queue active although FIFO is full.");
-
- if (cdev->is_peripheral) {
- kfree_skb(skb);
- dev->stats.tx_dropped++;
- return NETDEV_TX_OK;
- } else {
- return NETDEV_TX_BUSY;
- }
- }
-
/* get put index for frame */
putidx = cdev->tx_fifo_putidx;
@@ -1830,11 +1810,6 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev,
m_can_write(cdev, M_CAN_TXBAR, (1 << putidx));
cdev->tx_fifo_putidx = (++cdev->tx_fifo_putidx >= cdev->can.echo_skb_max ?
0 : cdev->tx_fifo_putidx);
-
- /* stop network queue if fifo full */
- if (m_can_tx_fifo_full(cdev) ||
- m_can_next_echo_skb_occupied(dev, putidx))
- netif_stop_queue(dev);
}
return NETDEV_TX_OK;
@@ -1868,14 +1843,16 @@ static void m_can_tx_queue_skb(struct m_can_classdev *cdev, struct sk_buff *skb)
static netdev_tx_t m_can_start_peripheral_xmit(struct m_can_classdev *cdev,
struct sk_buff *skb)
{
+ netdev_tx_t err;
+
if (cdev->can.state == CAN_STATE_BUS_OFF) {
m_can_clean(cdev->net);
return NETDEV_TX_OK;
}
- netif_stop_queue(cdev->net);
-
- m_can_start_tx(cdev);
+ err = m_can_start_tx(cdev);
+ if (err != NETDEV_TX_OK)
+ return err;
m_can_tx_queue_skb(cdev, skb);
@@ -1885,7 +1862,11 @@ static netdev_tx_t m_can_start_peripheral_xmit(struct m_can_classdev *cdev,
static netdev_tx_t m_can_start_fast_xmit(struct m_can_classdev *cdev,
struct sk_buff *skb)
{
- m_can_start_tx(cdev);
+ netdev_tx_t err;
+
+ err = m_can_start_tx(cdev);
+ if (err != NETDEV_TX_OK)
+ return err;
return m_can_tx_handler(cdev, skb);
}
--
2.39.2
m_can supports submitting multiple transmits with one register write.
This is an interesting option to reduce the number of SPI transfers for
peripheral chips.
The m_can_tx_op is extended with a bool that signals if it is the last
transmission and the submit should be executed immediately.
The worker then writes the skb to the FIFO and submits it only if the
submit bool is set. If it isn't set, the worker will write the next skb
which is waiting in the workqueue to the FIFO, etc.
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
Notes:
Notes:
- I ran into lost messages in the receive FIFO when using this
implementation. I guess this only shows up with my test setup in
loopback mode and maybe not enough CPU power.
- I put this behind the tx-frames ethtool coalescing option as we do
wait before submitting packages but it is something different than the
tx-frames-irq option. I am not sure if this is the correct option,
please let me know.
drivers/net/can/m_can/m_can.c | 55 ++++++++++++++++++++++++++++++++---
drivers/net/can/m_can/m_can.h | 6 ++++
2 files changed, 57 insertions(+), 4 deletions(-)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index 63d6e95717e3..1f758894e122 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -1508,6 +1508,9 @@ static int m_can_start(struct net_device *dev)
if (ret)
return ret;
+ netdev_queue_set_dql_min_limit(netdev_get_tx_queue(cdev->net, 0),
+ cdev->tx_max_coalesced_frames);
+
cdev->can.state = CAN_STATE_ERROR_ACTIVE;
m_can_enable_all_interrupts(cdev);
@@ -1818,8 +1821,13 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev,
*/
can_put_echo_skb(skb, dev, putidx, frame_len);
- /* Enable TX FIFO element to start transfer */
- m_can_write(cdev, M_CAN_TXBAR, (1 << putidx));
+ if (cdev->is_peripheral) {
+ /* Delay enabling TX FIFO element */
+ cdev->tx_peripheral_submit |= BIT(putidx);
+ } else {
+ /* Enable TX FIFO element to start transfer */
+ m_can_write(cdev, M_CAN_TXBAR, BIT(putidx));
+ }
cdev->tx_fifo_putidx = (++cdev->tx_fifo_putidx >= cdev->can.echo_skb_max ?
0 : cdev->tx_fifo_putidx);
}
@@ -1832,6 +1840,17 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev,
return NETDEV_TX_BUSY;
}
+static void m_can_tx_submit(struct m_can_classdev *cdev)
+{
+ if (cdev->version == 30)
+ return;
+ if (!cdev->is_peripheral)
+ return;
+
+ m_can_write(cdev, M_CAN_TXBAR, cdev->tx_peripheral_submit);
+ cdev->tx_peripheral_submit = 0;
+}
+
static void m_can_tx_work_queue(struct work_struct *ws)
{
struct m_can_tx_op *op = container_of(ws, struct m_can_tx_op, work);
@@ -1840,11 +1859,15 @@ static void m_can_tx_work_queue(struct work_struct *ws)
op->skb = NULL;
m_can_tx_handler(cdev, skb);
+ if (op->submit)
+ m_can_tx_submit(cdev);
}
-static void m_can_tx_queue_skb(struct m_can_classdev *cdev, struct sk_buff *skb)
+static void m_can_tx_queue_skb(struct m_can_classdev *cdev, struct sk_buff *skb,
+ bool submit)
{
cdev->tx_ops[cdev->next_tx_op].skb = skb;
+ cdev->tx_ops[cdev->next_tx_op].submit = submit;
queue_work(cdev->tx_wq, &cdev->tx_ops[cdev->next_tx_op].work);
++cdev->next_tx_op;
@@ -1856,6 +1879,7 @@ static netdev_tx_t m_can_start_peripheral_xmit(struct m_can_classdev *cdev,
struct sk_buff *skb)
{
netdev_tx_t err;
+ bool submit;
if (cdev->can.state == CAN_STATE_BUS_OFF) {
m_can_clean(cdev->net);
@@ -1866,7 +1890,15 @@ static netdev_tx_t m_can_start_peripheral_xmit(struct m_can_classdev *cdev,
if (err != NETDEV_TX_OK)
return err;
- m_can_tx_queue_skb(cdev, skb);
+ ++cdev->nr_txs_without_submit;
+ if (cdev->nr_txs_without_submit >= cdev->tx_max_coalesced_frames ||
+ !netdev_xmit_more()) {
+ cdev->nr_txs_without_submit = 0;
+ submit = true;
+ } else {
+ submit = false;
+ }
+ m_can_tx_queue_skb(cdev, skb, submit);
return NETDEV_TX_OK;
}
@@ -1998,6 +2030,7 @@ static int m_can_get_coalesce(struct net_device *dev,
ec->rx_max_coalesced_frames_irq = cdev->rx_max_coalesced_frames_irq;
ec->rx_coalesce_usecs_irq = cdev->rx_coalesce_usecs_irq;
+ ec->tx_max_coalesced_frames = cdev->tx_max_coalesced_frames;
ec->tx_max_coalesced_frames_irq = cdev->tx_max_coalesced_frames_irq;
ec->tx_coalesce_usecs_irq = cdev->tx_coalesce_usecs_irq;
@@ -2042,6 +2075,18 @@ static int m_can_set_coalesce(struct net_device *dev,
netdev_err(dev, "tx-frames-irq and tx-usecs-irq can only be set together\n");
return -EINVAL;
}
+ if (ec->tx_max_coalesced_frames > cdev->mcfg[MRAM_TXE].num) {
+ netdev_err(dev, "tx-frames %u greater than the TX event FIFO %u\n",
+ ec->tx_max_coalesced_frames,
+ cdev->mcfg[MRAM_TXE].num);
+ return -EINVAL;
+ }
+ if (ec->tx_max_coalesced_frames > cdev->mcfg[MRAM_TXB].num) {
+ netdev_err(dev, "tx-frames %u greater than the TX FIFO %u\n",
+ ec->tx_max_coalesced_frames,
+ cdev->mcfg[MRAM_TXB].num);
+ return -EINVAL;
+ }
if (ec->rx_coalesce_usecs_irq != 0 && ec->tx_coalesce_usecs_irq != 0 &&
ec->rx_coalesce_usecs_irq != ec->tx_coalesce_usecs_irq) {
netdev_err(dev, "rx-usecs-irq %u needs to be equal to tx-usecs-irq %u if both are enabled\n",
@@ -2052,6 +2097,7 @@ static int m_can_set_coalesce(struct net_device *dev,
cdev->rx_max_coalesced_frames_irq = ec->rx_max_coalesced_frames_irq;
cdev->rx_coalesce_usecs_irq = ec->rx_coalesce_usecs_irq;
+ cdev->tx_max_coalesced_frames = ec->tx_max_coalesced_frames;
cdev->tx_max_coalesced_frames_irq = ec->tx_max_coalesced_frames_irq;
cdev->tx_coalesce_usecs_irq = ec->tx_coalesce_usecs_irq;
@@ -2069,6 +2115,7 @@ static const struct ethtool_ops m_can_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS_IRQ |
ETHTOOL_COALESCE_RX_MAX_FRAMES_IRQ |
ETHTOOL_COALESCE_TX_USECS_IRQ |
+ ETHTOOL_COALESCE_TX_MAX_FRAMES |
ETHTOOL_COALESCE_TX_MAX_FRAMES_IRQ,
.get_ts_info = ethtool_op_get_ts_info,
.get_coalesce = m_can_get_coalesce,
diff --git a/drivers/net/can/m_can/m_can.h b/drivers/net/can/m_can/m_can.h
index e230cf320a6c..a2a6d10015fd 100644
--- a/drivers/net/can/m_can/m_can.h
+++ b/drivers/net/can/m_can/m_can.h
@@ -74,6 +74,7 @@ struct m_can_tx_op {
struct m_can_classdev *cdev;
struct work_struct work;
struct sk_buff *skb;
+ bool submit;
};
struct m_can_classdev {
@@ -103,6 +104,7 @@ struct m_can_classdev {
u32 active_interrupts;
u32 rx_max_coalesced_frames_irq;
u32 rx_coalesce_usecs_irq;
+ u32 tx_max_coalesced_frames;
u32 tx_max_coalesced_frames_irq;
u32 tx_coalesce_usecs_irq;
@@ -117,6 +119,10 @@ struct m_can_classdev {
int tx_fifo_size;
int next_tx_op;
+ int nr_txs_without_submit;
+ /* bitfield of fifo elements that will be submitted together */
+ u32 tx_peripheral_submit;
+
struct mram_cfg mcfg[MRAM_CFG_NUM];
};
--
2.39.2
Implement byte queue limiting in preparation for the use of xmit_more().
Signed-off-by: Markus Schneider-Pargmann <[email protected]>
---
drivers/net/can/m_can/m_can.c | 49 +++++++++++++++++++++++++----------
1 file changed, 35 insertions(+), 14 deletions(-)
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index 3cb3d01e1a61..63d6e95717e3 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -445,6 +445,8 @@ static void m_can_clean(struct net_device *net)
for (int i = 0; i != cdev->can.echo_skb_max; ++i)
can_free_echo_skb(cdev->net, i, NULL);
+ netdev_reset_queue(cdev->net);
+
spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
cdev->tx_fifo_in_flight = 0;
spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
@@ -999,29 +1001,34 @@ static int m_can_poll(struct napi_struct *napi, int quota)
* echo. timestamp is used for peripherals to ensure correct ordering
* by rx-offload, and is ignored for non-peripherals.
*/
-static void m_can_tx_update_stats(struct m_can_classdev *cdev,
- unsigned int msg_mark,
- u32 timestamp)
+static unsigned int m_can_tx_update_stats(struct m_can_classdev *cdev,
+ unsigned int msg_mark, u32 timestamp)
{
struct net_device *dev = cdev->net;
struct net_device_stats *stats = &dev->stats;
+ unsigned int frame_len;
if (cdev->is_peripheral)
stats->tx_bytes +=
can_rx_offload_get_echo_skb(&cdev->offload,
msg_mark,
timestamp,
- NULL);
+ &frame_len);
else
- stats->tx_bytes += can_get_echo_skb(dev, msg_mark, NULL);
+ stats->tx_bytes += can_get_echo_skb(dev, msg_mark, &frame_len);
stats->tx_packets++;
+
+ return frame_len;
}
-static void m_can_finish_tx(struct m_can_classdev *cdev, int transmitted)
+static void m_can_finish_tx(struct m_can_classdev *cdev, int transmitted,
+ int transmitted_frame_len)
{
unsigned long irqflags;
+ netdev_completed_queue(cdev->net, transmitted, transmitted_frame_len);
+
spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
if (cdev->tx_fifo_in_flight >= cdev->tx_fifo_size && transmitted > 0)
netif_wake_queue(cdev->net);
@@ -1060,6 +1067,7 @@ static int m_can_echo_tx_event(struct net_device *dev)
int err = 0;
unsigned int msg_mark;
int processed = 0;
+ int processed_frame_len = 0;
struct m_can_classdev *cdev = netdev_priv(dev);
@@ -1088,7 +1096,9 @@ static int m_can_echo_tx_event(struct net_device *dev)
fgi = (++fgi >= cdev->mcfg[MRAM_TXE].num ? 0 : fgi);
/* update stats */
- m_can_tx_update_stats(cdev, msg_mark, timestamp);
+ processed_frame_len += m_can_tx_update_stats(cdev, msg_mark,
+ timestamp);
+
++processed;
}
@@ -1096,7 +1106,7 @@ static int m_can_echo_tx_event(struct net_device *dev)
m_can_write(cdev, M_CAN_TXEFA, FIELD_PREP(TXEFA_EFAI_MASK,
ack_fgi));
- m_can_finish_tx(cdev, processed);
+ m_can_finish_tx(cdev, processed, processed_frame_len);
return err;
}
@@ -1187,11 +1197,12 @@ static irqreturn_t m_can_isr(int irq, void *dev_id)
if (ir & IR_TC) {
/* Transmission Complete Interrupt*/
u32 timestamp = 0;
+ unsigned int frame_len;
if (cdev->is_peripheral)
timestamp = m_can_get_timestamp(cdev);
- m_can_tx_update_stats(cdev, 0, timestamp);
- m_can_finish_tx(cdev, 1);
+ frame_len = m_can_tx_update_stats(cdev, 0, timestamp);
+ m_can_finish_tx(cdev, 1, frame_len);
}
} else {
if (ir & (IR_TEFN | IR_TEFW)) {
@@ -1720,6 +1731,7 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev,
u32 cccr, fdflags;
int err;
int putidx;
+ unsigned int frame_len = can_skb_get_frame_len(skb);
/* Generate ID field for TX buffer Element */
/* Common to all supported M_CAN versions */
@@ -1765,7 +1777,7 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev,
}
m_can_write(cdev, M_CAN_TXBTIE, 0x1);
- can_put_echo_skb(skb, dev, 0, 0);
+ can_put_echo_skb(skb, dev, 0, frame_len);
m_can_write(cdev, M_CAN_TXBAR, 0x1);
/* End of xmit function for version 3.0.x */
@@ -1804,7 +1816,7 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev,
/* Push loopback echo.
* Will be looped back on TX interrupt based on message marker
*/
- can_put_echo_skb(skb, dev, putidx, 0);
+ can_put_echo_skb(skb, dev, putidx, frame_len);
/* Enable TX FIFO element to start transfer */
m_can_write(cdev, M_CAN_TXBAR, (1 << putidx));
@@ -1875,14 +1887,23 @@ static netdev_tx_t m_can_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct m_can_classdev *cdev = netdev_priv(dev);
+ netdev_tx_t ret;
+ unsigned int frame_len;
if (can_dev_dropped_skb(dev, skb))
return NETDEV_TX_OK;
+ frame_len = can_skb_get_frame_len(skb);
+
if (cdev->is_peripheral)
- return m_can_start_peripheral_xmit(cdev, skb);
+ ret = m_can_start_peripheral_xmit(cdev, skb);
else
- return m_can_start_fast_xmit(cdev, skb);
+ ret = m_can_start_fast_xmit(cdev, skb);
+
+ if (ret == NETDEV_TX_OK)
+ netdev_sent_queue(dev, frame_len);
+
+ return ret;
}
static int m_can_open(struct net_device *dev)
--
2.39.2
On Wed, Mar 15, 2023 at 12:05:31PM +0100, Markus Schneider-Pargmann wrote:
> Merge both if-blocks to fix this.
>
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
Reviewed-by: Simon Horman <[email protected]>
On Wed, Mar 15, 2023 at 12:05:32PM +0100, Markus Schneider-Pargmann wrote:
> The code already exits the function on !ir before this condition. No
> need to check again if anything is set as IR_ALL_INT is 0xffffffff.
>
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
Reviewed-by: Simon Horman <[email protected]>
On Wed, Mar 15, 2023 at 12:05:33PM +0100, Markus Schneider-Pargmann wrote:
> Interrupts are enabled a few lines further down as well. Remove this
> second call to enable all interrupts.
nit: maybe 'duplicate' reads better than 'second', as this call comes first.
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
> ---
> drivers/net/can/m_can/m_can.c | 1 -
> 1 file changed, 1 deletion(-)
>
> diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
> index 8eb327ae3bdf..5274d9642566 100644
> --- a/drivers/net/can/m_can/m_can.c
> +++ b/drivers/net/can/m_can/m_can.c
> @@ -1364,7 +1364,6 @@ static int m_can_chip_config(struct net_device *dev)
> m_can_write(cdev, M_CAN_TEST, test);
>
> /* Enable interrupts */
> - m_can_write(cdev, M_CAN_IR, IR_ALL_INT);
> if (!(cdev->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING))
> if (cdev->version == 30)
> m_can_write(cdev, M_CAN_IE, IR_ALL_INT &
> --
> 2.39.2
>
On Thu, Mar 16, 2023 at 10:09:45AM +0100, Simon Horman wrote:
> On Wed, Mar 15, 2023 at 12:05:33PM +0100, Markus Schneider-Pargmann wrote:
> > Interrupts are enabled a few lines further down as well. Remove this
> > second call to enable all interrupts.
>
> nit: maybe 'duplicate' reads better than 'second', as this call comes first.
I didn't mean to imply this should block progress.
Reviewed-by: Simon Horman <[email protected]>
>
> > Signed-off-by: Markus Schneider-Pargmann <[email protected]>
> > ---
> > drivers/net/can/m_can/m_can.c | 1 -
> > 1 file changed, 1 deletion(-)
> >
> > diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
> > index 8eb327ae3bdf..5274d9642566 100644
> > --- a/drivers/net/can/m_can/m_can.c
> > +++ b/drivers/net/can/m_can/m_can.c
> > @@ -1364,7 +1364,6 @@ static int m_can_chip_config(struct net_device *dev)
> > m_can_write(cdev, M_CAN_TEST, test);
> >
> > /* Enable interrupts */
> > - m_can_write(cdev, M_CAN_IR, IR_ALL_INT);
> > if (!(cdev->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING))
> > if (cdev->version == 30)
> > m_can_write(cdev, M_CAN_IE, IR_ALL_INT &
> > --
> > 2.39.2
> >
On Wed, Mar 15, 2023 at 12:05:34PM +0100, Markus Schneider-Pargmann wrote:
> There are a number of interrupts that are not used by the driver at the
> moment. Disable all of these.
>
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
> ---
> drivers/net/can/m_can/m_can.c | 18 +++++++++++-------
> 1 file changed, 11 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
> index 5274d9642566..e7aceeba3759 100644
> --- a/drivers/net/can/m_can/m_can.c
> +++ b/drivers/net/can/m_can/m_can.c
> @@ -1261,6 +1261,7 @@ static int m_can_set_bittiming(struct net_device *dev)
> static int m_can_chip_config(struct net_device *dev)
> {
> struct m_can_classdev *cdev = netdev_priv(dev);
> + u32 interrupts = IR_ALL_INT;
> u32 cccr, test;
> int err;
>
> @@ -1270,6 +1271,11 @@ static int m_can_chip_config(struct net_device *dev)
> return err;
> }
>
> + /* Disable unused interrupts */
> + interrupts &= ~(IR_ARA | IR_ELO | IR_DRX | IR_TEFF | IR_TEFW | IR_TFE |
> + IR_TCF | IR_HPM | IR_RF1F | IR_RF1W | IR_RF1N |
> + IR_RF0F | IR_RF0W);
> +
> m_can_config_endisable(cdev, true);
>
> /* RX Buffer/FIFO Element Size 64 bytes data field */
> @@ -1364,15 +1370,13 @@ static int m_can_chip_config(struct net_device *dev)
> m_can_write(cdev, M_CAN_TEST, test);
>
> /* Enable interrupts */
> - if (!(cdev->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING))
> + if (!(cdev->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)) {
> if (cdev->version == 30)
> - m_can_write(cdev, M_CAN_IE, IR_ALL_INT &
> - ~(IR_ERR_LEC_30X));
> + interrupts &= ~(IR_ERR_LEC_30X);
> else
> - m_can_write(cdev, M_CAN_IE, IR_ALL_INT &
> - ~(IR_ERR_LEC_31X));
> - else
> - m_can_write(cdev, M_CAN_IE, IR_ALL_INT);
> + interrupts &= ~(IR_ERR_LEC_31X);
> + }
> + m_can_write(cdev, M_CAN_IE, interrupts);
This now enables interrupts outside the if condition.
Which was also the case prior to patch 3/16.
Perhaps it makes sense to merge patches 3/16 and 4/16?
Perhaps not :)
Regardless,
Reviewed-by: Simon Horman <[email protected]>
On Wed, Mar 15, 2023 at 12:05:36PM +0100, Markus Schneider-Pargmann wrote:
> Combine header and data before writing to the transmit fifo to reduce
> the overhead for peripheral chips.
>
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
Thanks for addressing my comments on v2.
> ---
> drivers/net/can/m_can/m_can.c | 11 ++++++-----
> 1 file changed, 6 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
> index a5003435802b..35a2332464e5 100644
> --- a/drivers/net/can/m_can/m_can.c
> +++ b/drivers/net/can/m_can/m_can.c
> @@ -1681,6 +1681,8 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev)
> m_can_write(cdev, M_CAN_TXBAR, 0x1);
> /* End of xmit function for version 3.0.x */
> } else {
> + char buf[TXB_ELEMENT_SIZE];
> + u8 len_padded = DIV_ROUND_UP(cf->len, 4);
> /* Transmit routine for version >= v3.1.x */
>
> txfqs = m_can_read(cdev, M_CAN_TXFQS);
> @@ -1720,12 +1722,11 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev)
> fifo_header.dlc = FIELD_PREP(TX_BUF_MM_MASK, putidx) |
> FIELD_PREP(TX_BUF_DLC_MASK, can_fd_len2dlc(cf->len)) |
> fdflags | TX_BUF_EFC;
> - err = m_can_fifo_write(cdev, putidx, M_CAN_FIFO_ID, &fifo_header, 2);
> - if (err)
> - goto out_fail;
> + memcpy(buf, &fifo_header, 8);
> + memcpy_and_pad(&buf[8], len_padded, &cf->data, cf->len, 0);
I'm probably missing something obvious here but I'm seeing:
* len_padded is the number of 4-byte words
* but the 2nd argument to memcpy_and_pad should be a length in bytes
* so perhaps it should be: len_padded * 4
>
> - err = m_can_fifo_write(cdev, putidx, M_CAN_FIFO_DATA,
> - cf->data, DIV_ROUND_UP(cf->len, 4));
> + err = m_can_fifo_write(cdev, putidx, M_CAN_FIFO_ID,
> + buf, 2 + len_padded);
This part looks good to me :)
> if (err)
> goto out_fail;
>
> --
> 2.39.2
>
On Wed, Mar 15, 2023 at 12:05:41PM +0100, Markus Schneider-Pargmann wrote:
> m_can_tx_handler is the only place where data is written to the tx fifo.
> We can calculate the putidx in the driver code here to avoid the
> dependency on the txfqs register.
>
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
> ---
> drivers/net/can/m_can/m_can.c | 8 +++++++-
> drivers/net/can/m_can/m_can.h | 3 +++
> 2 files changed, 10 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
> index 4e794166664a..d5bcce948d2c 100644
> --- a/drivers/net/can/m_can/m_can.c
> +++ b/drivers/net/can/m_can/m_can.c
> @@ -1475,6 +1475,10 @@ static int m_can_start(struct net_device *dev)
>
> m_can_enable_all_interrupts(cdev);
>
> + if (cdev->version > 30)
> + cdev->tx_fifo_putidx = FIELD_GET(TXFQS_TFQPI_MASK,
> + m_can_read(cdev, M_CAN_TXFQS));
> +
> return 0;
> }
>
> @@ -1765,7 +1769,7 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev)
> }
>
> /* get put index for frame */
> - putidx = FIELD_GET(TXFQS_TFQPI_MASK, txfqs);
> + putidx = cdev->tx_fifo_putidx;
>
> /* Construct DLC Field, with CAN-FD configuration.
> * Use the put index of the fifo as the message marker,
> @@ -1798,6 +1802,8 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev)
>
> /* Enable TX FIFO element to start transfer */
> m_can_write(cdev, M_CAN_TXBAR, (1 << putidx));
> + cdev->tx_fifo_putidx = (++cdev->tx_fifo_putidx >= cdev->can.echo_skb_max ?
> + 0 : cdev->tx_fifo_putidx);
>
> /* stop network queue if fifo full */
> if (m_can_tx_fifo_full(cdev) ||
> diff --git a/drivers/net/can/m_can/m_can.h b/drivers/net/can/m_can/m_can.h
> index d0c21eddb6ec..548ae908ac4e 100644
> --- a/drivers/net/can/m_can/m_can.h
> +++ b/drivers/net/can/m_can/m_can.h
> @@ -102,6 +102,9 @@ struct m_can_classdev {
> u32 tx_max_coalesced_frames_irq;
> u32 tx_coalesce_usecs_irq;
>
> + // Store this internally to avoid fetch delays on peripheral chips
> + int tx_fifo_putidx;
nit: it might be slightly nicer to do a pass over the code
and make putidx unsigned - assuming it is an unsigned value.
> +
> struct mram_cfg mcfg[MRAM_CFG_NUM];
> };
>
> --
> 2.39.2
>
On Wed, Mar 15, 2023 at 12:05:35PM +0100, Markus Schneider-Pargmann wrote:
> Interrupts currently get disabled if the interrupt status shows new
> received data. Non-peripheral chips handle receiving in a worker thread,
> but peripheral chips are handling the receive process in the threaded
> interrupt routine itself without scheduling it for a different worker.
> So there is no need to disable interrupts for peripheral chips.
>
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
Reviewed-by: Simon Horman <[email protected]>
On Wed, Mar 15, 2023 at 12:05:37PM +0100, Markus Schneider-Pargmann wrote:
> m_can offers the possibility to set an interrupt on reaching a watermark
> level in the receive FIFO. This can be used to implement coalescing.
> Unfortunately there is no hardware timeout available to trigger an
> interrupt if only a few messages were received within a given time. To
> solve this I am using a hrtimer to wake up the irq thread after x
> microseconds.
>
> The timer is always started if receive coalescing is enabled and new
> received frames were available during an interrupt. The timer is stopped
> if during a interrupt handling no new data was available.
>
> If the timer is started the new item interrupt is disabled and the
> watermark interrupt takes over. If the timer is not started again, the
> new item interrupt is enabled again, notifying the handler about every
> new item received.
>
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
Reviewed-by: Simon Horman <[email protected]>
On Wed, Mar 15, 2023 at 12:05:39PM +0100, Markus Schneider-Pargmann wrote:
> Add the possibility to set coalescing parameters with ethtool.
>
> rx-frames-irq and rx-usecs-irq can only be set and unset together as the
> implemented mechanism would not work otherwise. rx-frames-irq can't be
> greater than the RX FIFO size.
>
> Also all values can only be changed if the chip is not active.
>
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
Nit below not withstanding,
Reviewed-by: Simon Horman <[email protected]>
> ---
> drivers/net/can/m_can/m_can.c | 46 +++++++++++++++++++++++++++++++++++
> 1 file changed, 46 insertions(+)
>
> diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
> index 94c962ac6992..7f8decfae81e 100644
> --- a/drivers/net/can/m_can/m_can.c
> +++ b/drivers/net/can/m_can/m_can.c
...
> +static int m_can_set_coalesce(struct net_device *dev,
> + struct ethtool_coalesce *ec,
> + struct kernel_ethtool_coalesce *kec,
> + struct netlink_ext_ack *ext_ack)
> +{
> + struct m_can_classdev *cdev = netdev_priv(dev);
> +
> + if (cdev->can.state != CAN_STATE_STOPPED) {
> + netdev_err(dev, "Device is in use, please shut it down first\n");
> + return -EBUSY;
> + }
> +
> + if (ec->rx_max_coalesced_frames_irq > cdev->mcfg[MRAM_RXF0].num) {
> + netdev_err(dev, "rx-frames-irq %u greater than the RX FIFO %u\n",
> + ec->rx_max_coalesced_frames_irq,
> + cdev->mcfg[MRAM_RXF0].num);
> + return -EINVAL;
> + }
> + if ((ec->rx_max_coalesced_frames_irq == 0) != (ec->rx_coalesce_usecs_irq == 0)) {
nit: checkpatch complains about unnecessary parentheses on the line above.
drivers/net/can/m_can/m_can.c:1970: CHECK: Unnecessary parentheses around 'ec->rx_max_coalesced_frames_irq == 0'
+ if ((ec->rx_max_coalesced_frames_irq == 0) != (ec->rx_coalesce_usecs_irq == 0)) {
drivers/net/can/m_can/m_can.c:1970: CHECK: Unnecessary parentheses around 'ec->rx_coalesce_usecs_irq == 0'
+ if ((ec->rx_max_coalesced_frames_irq == 0) != (ec->rx_coalesce_usecs_irq == 0)) {
> + netdev_err(dev, "rx-frames-irq and rx-usecs-irq can only be set together\n");
> + return -EINVAL;
> + }
> +
> + cdev->rx_max_coalesced_frames_irq = ec->rx_max_coalesced_frames_irq;
> + cdev->rx_coalesce_usecs_irq = ec->rx_coalesce_usecs_irq;
> +
> + return 0;
> +}
...
On Wed, Mar 15, 2023 at 12:05:38PM +0100, Markus Schneider-Pargmann wrote:
> Extend the coalescing implementation for transmits.
>
> In normal mode the chip raises an interrupt for every finished transmit.
> This implementation switches to coalescing mode as soon as an interrupt
> handled a transmit. For coalescing the watermark level interrupt is used
> to interrupt exactly after x frames were sent. It switches back into
> normal mode once there was an interrupt with no finished transmit and
> the timer being inactive.
>
> The timer is shared with receive coalescing. The time for receive and
> transmit coalescing timers have to be the same for that to work. The
> benefit is to have only a single running timer.
>
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
Reviewed-by: Simon Horman <[email protected]>
On Wed, Mar 15, 2023 at 12:05:40PM +0100, Markus Schneider-Pargmann wrote:
> Add get/set functions for ethtool coalescing. tx-frames-irq and
> tx-usecs-irq can only be set/unset together. tx-frames-irq needs to be
> less than TXE and TXB.
Perhaps I'm reading this wrong (maybe I need a coffee). But I think it
might be a bit clearer to call out the TX aspect of this patch up front (I
know it is in the subject.
Maybe something like this:
Add TX support to get/set functions for ethtool coalescing...
>
> As rx and tx share the same timer, rx-usecs-irq and tx-usecs-irq can be
> enabled/disabled individually but they need to have the same value if
> enabled.
>
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
Nits above and below not withstanding,
Reviewed-by: Simon Horman <[email protected]>
> ---
> drivers/net/can/m_can/m_can.c | 38 ++++++++++++++++++++++++++++++++++-
> 1 file changed, 37 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
> index 7f8decfae81e..4e794166664a 100644
> --- a/drivers/net/can/m_can/m_can.c
> +++ b/drivers/net/can/m_can/m_can.c
> @@ -1945,6 +1945,8 @@ static int m_can_get_coalesce(struct net_device *dev,
>
> ec->rx_max_coalesced_frames_irq = cdev->rx_max_coalesced_frames_irq;
> ec->rx_coalesce_usecs_irq = cdev->rx_coalesce_usecs_irq;
> + ec->tx_max_coalesced_frames_irq = cdev->tx_max_coalesced_frames_irq;
> + ec->tx_coalesce_usecs_irq = cdev->tx_coalesce_usecs_irq;
>
> return 0;
> }
> @@ -1971,16 +1973,50 @@ static int m_can_set_coalesce(struct net_device *dev,
> netdev_err(dev, "rx-frames-irq and rx-usecs-irq can only be set together\n");
> return -EINVAL;
> }
> + if (ec->tx_max_coalesced_frames_irq > cdev->mcfg[MRAM_TXE].num) {
> + netdev_err(dev, "tx-frames-irq %u greater than the TX event FIFO %u\n",
> + ec->tx_max_coalesced_frames_irq,
> + cdev->mcfg[MRAM_TXE].num);
> + return -EINVAL;
> + }
> + if (ec->tx_max_coalesced_frames_irq > cdev->mcfg[MRAM_TXB].num) {
> + netdev_err(dev, "tx-frames-irq %u greater than the TX FIFO %u\n",
> + ec->tx_max_coalesced_frames_irq,
> + cdev->mcfg[MRAM_TXB].num);
> + return -EINVAL;
> + }
> + if ((ec->tx_max_coalesced_frames_irq == 0) != (ec->tx_coalesce_usecs_irq == 0)) {
> + netdev_err(dev, "tx-frames-irq and tx-usecs-irq can only be set together\n");
> + return -EINVAL;
> + }
nit: checkpatch complains about unnecessary parentheses
drivers/net/can/m_can/m_can.c:1988: CHECK: Unnecessary parentheses around 'ec->tx_max_coalesced_frames_irq == 0'
+ if ((ec->tx_max_coalesced_frames_irq == 0) != (ec->tx_coalesce_usecs_irq == 0)) {
drivers/net/can/m_can/m_can.c:1988: CHECK: Unnecessary parentheses around 'ec->tx_coalesce_usecs_irq == 0'
+ if ((ec->tx_max_coalesced_frames_irq == 0) != (ec->tx_coalesce_usecs_irq == 0)) {
> + if (ec->rx_coalesce_usecs_irq != 0 && ec->tx_coalesce_usecs_irq != 0 &&
> + ec->rx_coalesce_usecs_irq != ec->tx_coalesce_usecs_irq) {
> + netdev_err(dev, "rx-usecs-irq %u needs to be equal to tx-usecs-irq %u if both are enabled\n",
> + ec->rx_coalesce_usecs_irq,
> + ec->tx_coalesce_usecs_irq);
> + return -EINVAL;
> + }
>
> cdev->rx_max_coalesced_frames_irq = ec->rx_max_coalesced_frames_irq;
> cdev->rx_coalesce_usecs_irq = ec->rx_coalesce_usecs_irq;
> + cdev->tx_max_coalesced_frames_irq = ec->tx_max_coalesced_frames_irq;
> + cdev->tx_coalesce_usecs_irq = ec->tx_coalesce_usecs_irq;
> +
> + if (cdev->rx_coalesce_usecs_irq)
> + cdev->irq_timer_wait =
> + ns_to_ktime(cdev->rx_coalesce_usecs_irq * NSEC_PER_USEC);
> + else
> + cdev->irq_timer_wait =
> + ns_to_ktime(cdev->tx_coalesce_usecs_irq * NSEC_PER_USEC);
nit: perhaps adding us_to_ktime() and using it treewide would be interesting
> return 0;
> }
>
> static const struct ethtool_ops m_can_ethtool_ops = {
> .supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS_IRQ |
> - ETHTOOL_COALESCE_RX_MAX_FRAMES_IRQ,
> + ETHTOOL_COALESCE_RX_MAX_FRAMES_IRQ |
> + ETHTOOL_COALESCE_TX_USECS_IRQ |
> + ETHTOOL_COALESCE_TX_MAX_FRAMES_IRQ,
> .get_ts_info = ethtool_op_get_ts_info,
> .get_coalesce = m_can_get_coalesce,
> .set_coalesce = m_can_set_coalesce,
> --
> 2.39.2
>
On Thu, Mar 16, 2023 at 10:55:00AM +0100, Simon Horman wrote:
> On Wed, Mar 15, 2023 at 12:05:41PM +0100, Markus Schneider-Pargmann wrote:
> > m_can_tx_handler is the only place where data is written to the tx fifo.
> > We can calculate the putidx in the driver code here to avoid the
> > dependency on the txfqs register.
> >
> > Signed-off-by: Markus Schneider-Pargmann <[email protected]>
Nit in my previous email (which I hit send on a little too soon)
not withstanding,
Signed-off-by: Simon Horman <[email protected]>
FWIIW, I am taking a pause in my review now.
...
> > diff --git a/drivers/net/can/m_can/m_can.h b/drivers/net/can/m_can/m_can.h
> > index d0c21eddb6ec..548ae908ac4e 100644
> > --- a/drivers/net/can/m_can/m_can.h
> > +++ b/drivers/net/can/m_can/m_can.h
> > @@ -102,6 +102,9 @@ struct m_can_classdev {
> > u32 tx_max_coalesced_frames_irq;
> > u32 tx_coalesce_usecs_irq;
> >
> > + // Store this internally to avoid fetch delays on peripheral chips
> > + int tx_fifo_putidx;
>
> nit: it might be slightly nicer to do a pass over the code
> and make putidx unsigned - assuming it is an unsigned value.
>
> > +
> > struct mram_cfg mcfg[MRAM_CFG_NUM];
> > };
> >
> > --
> > 2.39.2
> >
On Wed, Mar 15, 2023 at 12:05:43PM +0100, Markus Schneider-Pargmann wrote:
> Keep track of the number of transmits in flight.
>
> This patch prepares the driver to control the network interface queue
> based on this counter. By itself this counter be
> implemented with an atomic, but as we need to do other things in the
> critical sections later I am using a spinlock instead.
>
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
Nit, assuming the values are always positive, I think
that unsigned might be a more appropriate type than int
for the tx_fifo_in_flight field, and associated function
parameters and local variables.
That notwithstanding,
Reviewed-by: Simon Horman <[email protected]>
> ---
> drivers/net/can/m_can/m_can.c | 41 ++++++++++++++++++++++++++++++++++-
> drivers/net/can/m_can/m_can.h | 4 ++++
> 2 files changed, 44 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
> index 27d36bcc094c..4ad8f08f8284 100644
> --- a/drivers/net/can/m_can/m_can.c
> +++ b/drivers/net/can/m_can/m_can.c
> @@ -442,6 +442,7 @@ static u32 m_can_get_timestamp(struct m_can_classdev *cdev)
> static void m_can_clean(struct net_device *net)
> {
> struct m_can_classdev *cdev = netdev_priv(net);
> + unsigned long irqflags;
>
> for (int i = 0; i != cdev->tx_fifo_size; ++i) {
> if (!cdev->tx_ops[i].skb)
> @@ -453,6 +454,10 @@ static void m_can_clean(struct net_device *net)
>
> for (int i = 0; i != cdev->can.echo_skb_max; ++i)
> can_free_echo_skb(cdev->net, i, NULL);
> +
> + spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
> + cdev->tx_fifo_in_flight = 0;
> + spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
> }
>
> /* For peripherals, pass skb to rx-offload, which will push skb from
> @@ -1023,6 +1028,24 @@ static void m_can_tx_update_stats(struct m_can_classdev *cdev,
> stats->tx_packets++;
> }
>
> +static void m_can_finish_tx(struct m_can_classdev *cdev, int transmitted)
> +{
> + unsigned long irqflags;
> +
> + spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
> + cdev->tx_fifo_in_flight -= transmitted;
> + spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
> +}
> +
> +static void m_can_start_tx(struct m_can_classdev *cdev)
> +{
> + unsigned long irqflags;
> +
> + spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
> + ++cdev->tx_fifo_in_flight;
> + spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
> +}
> +
> static int m_can_echo_tx_event(struct net_device *dev)
> {
> u32 txe_count = 0;
> @@ -1032,6 +1055,7 @@ static int m_can_echo_tx_event(struct net_device *dev)
> int i = 0;
> int err = 0;
> unsigned int msg_mark;
> + int processed = 0;
>
> struct m_can_classdev *cdev = netdev_priv(dev);
>
> @@ -1061,12 +1085,15 @@ static int m_can_echo_tx_event(struct net_device *dev)
>
> /* update stats */
> m_can_tx_update_stats(cdev, msg_mark, timestamp);
> + ++processed;
> }
>
> if (ack_fgi != -1)
> m_can_write(cdev, M_CAN_TXEFA, FIELD_PREP(TXEFA_EFAI_MASK,
> ack_fgi));
>
> + m_can_finish_tx(cdev, processed);
> +
> return err;
> }
>
> @@ -1161,6 +1188,7 @@ static irqreturn_t m_can_isr(int irq, void *dev_id)
> timestamp = m_can_get_timestamp(cdev);
> m_can_tx_update_stats(cdev, 0, timestamp);
> netif_wake_queue(dev);
> + m_can_finish_tx(cdev, 1);
> }
> } else {
> if (ir & (IR_TEFN | IR_TEFW)) {
> @@ -1846,11 +1874,22 @@ static netdev_tx_t m_can_start_peripheral_xmit(struct m_can_classdev *cdev,
> }
>
> netif_stop_queue(cdev->net);
> +
> + m_can_start_tx(cdev);
> +
> m_can_tx_queue_skb(cdev, skb);
>
> return NETDEV_TX_OK;
> }
>
> +static netdev_tx_t m_can_start_fast_xmit(struct m_can_classdev *cdev,
> + struct sk_buff *skb)
> +{
> + m_can_start_tx(cdev);
> +
> + return m_can_tx_handler(cdev, skb);
> +}
> +
> static netdev_tx_t m_can_start_xmit(struct sk_buff *skb,
> struct net_device *dev)
> {
> @@ -1862,7 +1901,7 @@ static netdev_tx_t m_can_start_xmit(struct sk_buff *skb,
> if (cdev->is_peripheral)
> return m_can_start_peripheral_xmit(cdev, skb);
> else
> - return m_can_tx_handler(cdev, skb);
> + return m_can_start_fast_xmit(cdev, skb);
> }
>
> static int m_can_open(struct net_device *dev)
> diff --git a/drivers/net/can/m_can/m_can.h b/drivers/net/can/m_can/m_can.h
> index 2e1a52980a18..e230cf320a6c 100644
> --- a/drivers/net/can/m_can/m_can.h
> +++ b/drivers/net/can/m_can/m_can.h
> @@ -109,6 +109,10 @@ struct m_can_classdev {
> // Store this internally to avoid fetch delays on peripheral chips
> int tx_fifo_putidx;
>
> + /* Protects shared state between start_xmit and m_can_isr */
> + spinlock_t tx_handling_spinlock;
> + int tx_fifo_in_flight;
> +
> struct m_can_tx_op *tx_ops;
> int tx_fifo_size;
> int next_tx_op;
> --
> 2.39.2
>
On Wed, Mar 15, 2023 at 12:05:44PM +0100, Markus Schneider-Pargmann wrote:
> The network queue is currently always stopped in start_xmit and
> continued in the interrupt handler. This is not possible anymore if we
> want to keep multiple transmits in flight in parallel.
>
> Use the previously introduced tx_fifo_in_flight counter to control the
> network queue instead. This has the benefit of not needing to ask the
> hardware about fifo status.
>
> This patch stops the network queue in start_xmit if the number of
> transmits in flight reaches the size of the fifo and wakes up the queue
> from the interrupt handler once the transmits in flight drops below the
> fifo size. This means any skbs over the limit will be rejected
> immediately in start_xmit (it shouldn't be possible at all to reach that
> state anyways).
>
> The maximum number of transmits in flight is the size of the fifo.
>
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
> ---
> drivers/net/can/m_can/m_can.c | 71 +++++++++++++----------------------
> 1 file changed, 26 insertions(+), 45 deletions(-)
>
> diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
> index 4ad8f08f8284..3cb3d01e1a61 100644
> --- a/drivers/net/can/m_can/m_can.c
> +++ b/drivers/net/can/m_can/m_can.c
...
> @@ -1033,17 +1023,31 @@ static void m_can_finish_tx(struct m_can_classdev *cdev, int transmitted)
> unsigned long irqflags;
>
> spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
> + if (cdev->tx_fifo_in_flight >= cdev->tx_fifo_size && transmitted > 0)
> + netif_wake_queue(cdev->net);
> cdev->tx_fifo_in_flight -= transmitted;
> spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
> }
>
> -static void m_can_start_tx(struct m_can_classdev *cdev)
> +static netdev_tx_t m_can_start_tx(struct m_can_classdev *cdev)
> {
> unsigned long irqflags;
> + int tx_fifo_in_flight;
>
> spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
> - ++cdev->tx_fifo_in_flight;
> + tx_fifo_in_flight = cdev->tx_fifo_in_flight + 1;
> + if (tx_fifo_in_flight >= cdev->tx_fifo_size) {
> + netif_stop_queue(cdev->net);
> + if (tx_fifo_in_flight > cdev->tx_fifo_size) {
> + netdev_err(cdev->net, "hard_xmit called while TX FIFO full\n");
Perhaps I misunderstand things.
But it seems that this message is triggered in the datapath.
Thus I think it should be rate limited, or perhaps only logged once.
> + spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
> + return NETDEV_TX_BUSY;
> + }
> + }
> + cdev->tx_fifo_in_flight = tx_fifo_in_flight;
> spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
> +
> + return NETDEV_TX_OK;
> }
>
> static int m_can_echo_tx_event(struct net_device *dev)
...
> @@ -1830,11 +1810,6 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev,
> m_can_write(cdev, M_CAN_TXBAR, (1 << putidx));
> cdev->tx_fifo_putidx = (++cdev->tx_fifo_putidx >= cdev->can.echo_skb_max ?
> 0 : cdev->tx_fifo_putidx);
> -
> - /* stop network queue if fifo full */
> - if (m_can_tx_fifo_full(cdev) ||
> - m_can_next_echo_skb_occupied(dev, putidx))
> - netif_stop_queue(dev);
> }
smatch tells me that m_can_next_echo_skb_occupied is now defined but unused.
>
> return NETDEV_TX_OK;
...
On Wed, Mar 15, 2023 at 12:05:45PM +0100, Markus Schneider-Pargmann wrote:
> Implement byte queue limiting in preparation for the use of xmit_more().
>
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
Nits below aside, this looks good to me.
Reviewed-by: Simon Horman <[email protected]>
> ---
> drivers/net/can/m_can/m_can.c | 49 +++++++++++++++++++++++++----------
> 1 file changed, 35 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
> index 3cb3d01e1a61..63d6e95717e3 100644
> --- a/drivers/net/can/m_can/m_can.c
> +++ b/drivers/net/can/m_can/m_can.c
...
> @@ -999,29 +1001,34 @@ static int m_can_poll(struct napi_struct *napi, int quota)
> * echo. timestamp is used for peripherals to ensure correct ordering
> * by rx-offload, and is ignored for non-peripherals.
> */
> -static void m_can_tx_update_stats(struct m_can_classdev *cdev,
> - unsigned int msg_mark,
> - u32 timestamp)
> +static unsigned int m_can_tx_update_stats(struct m_can_classdev *cdev,
> + unsigned int msg_mark, u32 timestamp)
> {
> struct net_device *dev = cdev->net;
> struct net_device_stats *stats = &dev->stats;
> + unsigned int frame_len;
>
> if (cdev->is_peripheral)
> stats->tx_bytes +=
> can_rx_offload_get_echo_skb(&cdev->offload,
> msg_mark,
> timestamp,
> - NULL);
> + &frame_len);
> else
> - stats->tx_bytes += can_get_echo_skb(dev, msg_mark, NULL);
> + stats->tx_bytes += can_get_echo_skb(dev, msg_mark, &frame_len);
>
> stats->tx_packets++;
> +
> + return frame_len;
> }
>
> -static void m_can_finish_tx(struct m_can_classdev *cdev, int transmitted)
> +static void m_can_finish_tx(struct m_can_classdev *cdev, int transmitted,
> + int transmitted_frame_len)
nit: I think unsigned int would be a better type for transmitted_frame_len,
as that is the type of the 3rd argument to netdev_completed_queue()
> {
> unsigned long irqflags;
>
> + netdev_completed_queue(cdev->net, transmitted, transmitted_frame_len);
> +
> spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
> if (cdev->tx_fifo_in_flight >= cdev->tx_fifo_size && transmitted > 0)
> netif_wake_queue(cdev->net);
> @@ -1060,6 +1067,7 @@ static int m_can_echo_tx_event(struct net_device *dev)
> int err = 0;
> unsigned int msg_mark;
> int processed = 0;
> + int processed_frame_len = 0;
Likewise, here.
> struct m_can_classdev *cdev = netdev_priv(dev);
>
> @@ -1088,7 +1096,9 @@ static int m_can_echo_tx_event(struct net_device *dev)
> fgi = (++fgi >= cdev->mcfg[MRAM_TXE].num ? 0 : fgi);
>
> /* update stats */
> - m_can_tx_update_stats(cdev, msg_mark, timestamp);
> + processed_frame_len += m_can_tx_update_stats(cdev, msg_mark,
> + timestamp);
> +
> ++processed;
> }
>
> @@ -1096,7 +1106,7 @@ static int m_can_echo_tx_event(struct net_device *dev)
> m_can_write(cdev, M_CAN_TXEFA, FIELD_PREP(TXEFA_EFAI_MASK,
> ack_fgi));
>
> - m_can_finish_tx(cdev, processed);
> + m_can_finish_tx(cdev, processed, processed_frame_len);
>
> return err;
> }
...
On Wed, Mar 15, 2023 at 12:05:42PM +0100, Markus Schneider-Pargmann wrote:
> The current implementation uses the workqueue for peripheral chips to
> submit work. Only a single work item is queued and used at any time.
>
> To be able to keep more than one transmit in flight at a time, prepare
> the workqueue to support multiple transmits at the same time.
>
> Each work item now has a separate storage for a skb and a pointer to
> cdev. This assures that each workitem can be processed individually.
>
> The workqueue is replaced by an ordered workqueue which makes sure that
> only a single worker processes the items queued on the workqueue. Also
> items are ordered by the order they were enqueued. This removes most of
> the concurrency the workqueue normally offers. It is not necessary for
> this driver.
>
> The cleanup functions have to be adopted a bit to handle this new
> mechanism.
>
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
Reviewed-by: Simon Horman <[email protected]>
On 15.03.2023 12:05:36, Markus Schneider-Pargmann wrote:
> Combine header and data before writing to the transmit fifo to reduce
> the overhead for peripheral chips.
>
> Signed-off-by: Markus Schneider-Pargmann <[email protected]>
> ---
> drivers/net/can/m_can/m_can.c | 11 ++++++-----
> 1 file changed, 6 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
> index a5003435802b..35a2332464e5 100644
> --- a/drivers/net/can/m_can/m_can.c
> +++ b/drivers/net/can/m_can/m_can.c
> @@ -1681,6 +1681,8 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev)
> m_can_write(cdev, M_CAN_TXBAR, 0x1);
> /* End of xmit function for version 3.0.x */
> } else {
> + char buf[TXB_ELEMENT_SIZE];
Can you create a proper struct that describes a single FIFO entry (id,
dlc, data)? Use that struct instead of the struct id_and_dlc
fifo_header.
> + u8 len_padded = DIV_ROUND_UP(cf->len, 4);
> /* Transmit routine for version >= v3.1.x */
>
> txfqs = m_can_read(cdev, M_CAN_TXFQS);
> @@ -1720,12 +1722,11 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev)
> fifo_header.dlc = FIELD_PREP(TX_BUF_MM_MASK, putidx) |
> FIELD_PREP(TX_BUF_DLC_MASK, can_fd_len2dlc(cf->len)) |
> fdflags | TX_BUF_EFC;
> - err = m_can_fifo_write(cdev, putidx, M_CAN_FIFO_ID, &fifo_header, 2);
> - if (err)
> - goto out_fail;
> + memcpy(buf, &fifo_header, 8);
> + memcpy_and_pad(&buf[8], len_padded, &cf->data, cf->len, 0);
>
> - err = m_can_fifo_write(cdev, putidx, M_CAN_FIFO_DATA,
> - cf->data, DIV_ROUND_UP(cf->len, 4));
> + err = m_can_fifo_write(cdev, putidx, M_CAN_FIFO_ID,
> + buf, 2 + len_padded);
> if (err)
> goto out_fail;
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
On 15.03.2023 12:05:30, Markus Schneider-Pargmann wrote:
> Hi Marc and everyone,
>
> third version part 2, functionally I had to move from spin_lock to
> spin_lock_irqsave because of an interrupt that was calling start_xmit,
> see attached stack. This is tested on tcan455x but I don't have the
> integrated hardware myself so any testing is appreciated.
>
> The series implements many small and bigger throughput improvements and
> adds rx/tx coalescing at the end.
I've applied patches 1...5 to can-next.
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
Hi Marc and Simon,
On Fri, Mar 24, 2023 at 07:32:57PM +0100, Marc Kleine-Budde wrote:
> On 15.03.2023 12:05:30, Markus Schneider-Pargmann wrote:
> > Hi Marc and everyone,
> >
> > third version part 2, functionally I had to move from spin_lock to
> > spin_lock_irqsave because of an interrupt that was calling start_xmit,
> > see attached stack. This is tested on tcan455x but I don't have the
> > integrated hardware myself so any testing is appreciated.
> >
> > The series implements many small and bigger throughput improvements and
> > adds rx/tx coalescing at the end.
>
> I've applied patches 1...5 to can-next.
Thank you both for your feedback, I appreciate it. I am a progressing a
bit slower on this project right now but I will address your feedback.
Thanks,
Markus
Hi Simon,
On Thu, Mar 16, 2023 at 10:27:41AM +0100, Simon Horman wrote:
> On Wed, Mar 15, 2023 at 12:05:36PM +0100, Markus Schneider-Pargmann wrote:
> > Combine header and data before writing to the transmit fifo to reduce
> > the overhead for peripheral chips.
> >
> > Signed-off-by: Markus Schneider-Pargmann <[email protected]>
>
> Thanks for addressing my comments on v2.
>
> > ---
> > drivers/net/can/m_can/m_can.c | 11 ++++++-----
> > 1 file changed, 6 insertions(+), 5 deletions(-)
> >
> > diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
> > index a5003435802b..35a2332464e5 100644
> > --- a/drivers/net/can/m_can/m_can.c
> > +++ b/drivers/net/can/m_can/m_can.c
> > @@ -1681,6 +1681,8 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev)
> > m_can_write(cdev, M_CAN_TXBAR, 0x1);
> > /* End of xmit function for version 3.0.x */
> > } else {
> > + char buf[TXB_ELEMENT_SIZE];
> > + u8 len_padded = DIV_ROUND_UP(cf->len, 4);
> > /* Transmit routine for version >= v3.1.x */
> >
> > txfqs = m_can_read(cdev, M_CAN_TXFQS);
> > @@ -1720,12 +1722,11 @@ static netdev_tx_t m_can_tx_handler(struct m_can_classdev *cdev)
> > fifo_header.dlc = FIELD_PREP(TX_BUF_MM_MASK, putidx) |
> > FIELD_PREP(TX_BUF_DLC_MASK, can_fd_len2dlc(cf->len)) |
> > fdflags | TX_BUF_EFC;
> > - err = m_can_fifo_write(cdev, putidx, M_CAN_FIFO_ID, &fifo_header, 2);
> > - if (err)
> > - goto out_fail;
> > + memcpy(buf, &fifo_header, 8);
> > + memcpy_and_pad(&buf[8], len_padded, &cf->data, cf->len, 0);
>
> I'm probably missing something obvious here but I'm seeing:
>
> * len_padded is the number of 4-byte words
> * but the 2nd argument to memcpy_and_pad should be a length in bytes
> * so perhaps it should be: len_padded * 4
Thank you Simon for all the reviews, finally some time to continue on
this:
Thanks for pointing this out. I updated my script used for testing so I
catch something like this the next time. I will be using
TXB_ELEMENT_SIZE - 8 to reflect the buffer size and the 8 byte offset.
Best,
Markus
>
> >
> > - err = m_can_fifo_write(cdev, putidx, M_CAN_FIFO_DATA,
> > - cf->data, DIV_ROUND_UP(cf->len, 4));
> > + err = m_can_fifo_write(cdev, putidx, M_CAN_FIFO_ID,
> > + buf, 2 + len_padded);
>
> This part looks good to me :)
>
> > if (err)
> > goto out_fail;
> >
> > --
> > 2.39.2
> >
Hi Simon,
On Fri, Mar 17, 2023 at 05:02:44PM +0100, Simon Horman wrote:
> On Wed, Mar 15, 2023 at 12:05:43PM +0100, Markus Schneider-Pargmann wrote:
> > Keep track of the number of transmits in flight.
> >
> > This patch prepares the driver to control the network interface queue
> > based on this counter. By itself this counter be
> > implemented with an atomic, but as we need to do other things in the
> > critical sections later I am using a spinlock instead.
> >
> > Signed-off-by: Markus Schneider-Pargmann <[email protected]>
Thank you for all your reviews, very helpful.
>
> Nit, assuming the values are always positive, I think
> that unsigned might be a more appropriate type than int
> for the tx_fifo_in_flight field, and associated function
> parameters and local variables.
I agree that tx_fifo_in_flight is and should always be a positive value.
However as the code is operating with ++ and -- exclusively I would
personally prefer int here as that shows off-by-one errors much easier
in case there are any at some point.
Is that fine for you?
Best,
Markus
>
> That notwithstanding,
>
> Reviewed-by: Simon Horman <[email protected]>
>
> > ---
> > drivers/net/can/m_can/m_can.c | 41 ++++++++++++++++++++++++++++++++++-
> > drivers/net/can/m_can/m_can.h | 4 ++++
> > 2 files changed, 44 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
> > index 27d36bcc094c..4ad8f08f8284 100644
> > --- a/drivers/net/can/m_can/m_can.c
> > +++ b/drivers/net/can/m_can/m_can.c
> > @@ -442,6 +442,7 @@ static u32 m_can_get_timestamp(struct m_can_classdev *cdev)
> > static void m_can_clean(struct net_device *net)
> > {
> > struct m_can_classdev *cdev = netdev_priv(net);
> > + unsigned long irqflags;
> >
> > for (int i = 0; i != cdev->tx_fifo_size; ++i) {
> > if (!cdev->tx_ops[i].skb)
> > @@ -453,6 +454,10 @@ static void m_can_clean(struct net_device *net)
> >
> > for (int i = 0; i != cdev->can.echo_skb_max; ++i)
> > can_free_echo_skb(cdev->net, i, NULL);
> > +
> > + spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
> > + cdev->tx_fifo_in_flight = 0;
> > + spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
> > }
> >
> > /* For peripherals, pass skb to rx-offload, which will push skb from
> > @@ -1023,6 +1028,24 @@ static void m_can_tx_update_stats(struct m_can_classdev *cdev,
> > stats->tx_packets++;
> > }
> >
> > +static void m_can_finish_tx(struct m_can_classdev *cdev, int transmitted)
> > +{
> > + unsigned long irqflags;
> > +
> > + spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
> > + cdev->tx_fifo_in_flight -= transmitted;
> > + spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
> > +}
> > +
> > +static void m_can_start_tx(struct m_can_classdev *cdev)
> > +{
> > + unsigned long irqflags;
> > +
> > + spin_lock_irqsave(&cdev->tx_handling_spinlock, irqflags);
> > + ++cdev->tx_fifo_in_flight;
> > + spin_unlock_irqrestore(&cdev->tx_handling_spinlock, irqflags);
> > +}
> > +
> > static int m_can_echo_tx_event(struct net_device *dev)
> > {
> > u32 txe_count = 0;
> > @@ -1032,6 +1055,7 @@ static int m_can_echo_tx_event(struct net_device *dev)
> > int i = 0;
> > int err = 0;
> > unsigned int msg_mark;
> > + int processed = 0;
> >
> > struct m_can_classdev *cdev = netdev_priv(dev);
> >
> > @@ -1061,12 +1085,15 @@ static int m_can_echo_tx_event(struct net_device *dev)
> >
> > /* update stats */
> > m_can_tx_update_stats(cdev, msg_mark, timestamp);
> > + ++processed;
> > }
> >
> > if (ack_fgi != -1)
> > m_can_write(cdev, M_CAN_TXEFA, FIELD_PREP(TXEFA_EFAI_MASK,
> > ack_fgi));
> >
> > + m_can_finish_tx(cdev, processed);
> > +
> > return err;
> > }
> >
> > @@ -1161,6 +1188,7 @@ static irqreturn_t m_can_isr(int irq, void *dev_id)
> > timestamp = m_can_get_timestamp(cdev);
> > m_can_tx_update_stats(cdev, 0, timestamp);
> > netif_wake_queue(dev);
> > + m_can_finish_tx(cdev, 1);
> > }
> > } else {
> > if (ir & (IR_TEFN | IR_TEFW)) {
> > @@ -1846,11 +1874,22 @@ static netdev_tx_t m_can_start_peripheral_xmit(struct m_can_classdev *cdev,
> > }
> >
> > netif_stop_queue(cdev->net);
> > +
> > + m_can_start_tx(cdev);
> > +
> > m_can_tx_queue_skb(cdev, skb);
> >
> > return NETDEV_TX_OK;
> > }
> >
> > +static netdev_tx_t m_can_start_fast_xmit(struct m_can_classdev *cdev,
> > + struct sk_buff *skb)
> > +{
> > + m_can_start_tx(cdev);
> > +
> > + return m_can_tx_handler(cdev, skb);
> > +}
> > +
> > static netdev_tx_t m_can_start_xmit(struct sk_buff *skb,
> > struct net_device *dev)
> > {
> > @@ -1862,7 +1901,7 @@ static netdev_tx_t m_can_start_xmit(struct sk_buff *skb,
> > if (cdev->is_peripheral)
> > return m_can_start_peripheral_xmit(cdev, skb);
> > else
> > - return m_can_tx_handler(cdev, skb);
> > + return m_can_start_fast_xmit(cdev, skb);
> > }
> >
> > static int m_can_open(struct net_device *dev)
> > diff --git a/drivers/net/can/m_can/m_can.h b/drivers/net/can/m_can/m_can.h
> > index 2e1a52980a18..e230cf320a6c 100644
> > --- a/drivers/net/can/m_can/m_can.h
> > +++ b/drivers/net/can/m_can/m_can.h
> > @@ -109,6 +109,10 @@ struct m_can_classdev {
> > // Store this internally to avoid fetch delays on peripheral chips
> > int tx_fifo_putidx;
> >
> > + /* Protects shared state between start_xmit and m_can_isr */
> > + spinlock_t tx_handling_spinlock;
> > + int tx_fifo_in_flight;
> > +
> > struct m_can_tx_op *tx_ops;
> > int tx_fifo_size;
> > int next_tx_op;
> > --
> > 2.39.2
> >
On Tue, Jun 20, 2023 at 02:53:54PM +0200, Markus Schneider-Pargmann wrote:
> Hi Simon,
>
> On Fri, Mar 17, 2023 at 05:02:44PM +0100, Simon Horman wrote:
> > On Wed, Mar 15, 2023 at 12:05:43PM +0100, Markus Schneider-Pargmann wrote:
> > > Keep track of the number of transmits in flight.
> > >
> > > This patch prepares the driver to control the network interface queue
> > > based on this counter. By itself this counter be
> > > implemented with an atomic, but as we need to do other things in the
> > > critical sections later I am using a spinlock instead.
> > >
> > > Signed-off-by: Markus Schneider-Pargmann <[email protected]>
>
> Thank you for all your reviews, very helpful.
>
> >
> > Nit, assuming the values are always positive, I think
> > that unsigned might be a more appropriate type than int
> > for the tx_fifo_in_flight field, and associated function
> > parameters and local variables.
>
> I agree that tx_fifo_in_flight is and should always be a positive value.
> However as the code is operating with ++ and -- exclusively I would
> personally prefer int here as that shows off-by-one errors much easier
> in case there are any at some point.
>
> Is that fine for you?
Yes, I think so.