Skip to content

Commit 1c9a2d1

Browse files
can: add TX cancel mechanism to avoid priority inversion
Introduce a priority-based CAN TX cancel mechanism to reorder pending frames and prevent priority inversion during message transmission. Signed-off-by: zhaohaiyang1 <zhaohaiyang1@xiaomi.com>
1 parent 090860a commit 1c9a2d1

File tree

5 files changed

+186
-3
lines changed

5 files changed

+186
-3
lines changed

drivers/can/Kconfig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,22 @@ config CAN_TXPRIORITY
134134
---help---
135135
Prioritize sending based on canid.
136136

137+
config CAN_TXCANCEL
138+
bool "Implement tx cancel ability"
139+
default n
140+
depends on CAN_TXPRIORITY
141+
---help---
142+
Do not use cancel ability if lower driver enable H/W FIFO
143+
to store msg.
144+
145+
CAN_TXCANCEL relies on the hardware buffer has the following
146+
capability, Cancels the transmission of frames in the hardware
147+
buffer.
148+
149+
Enabling this feature adds support for the can cancel ability.
150+
this ability can cancel the msg with the largest msgID in the
151+
mailbox and return true if success.
152+
137153
choice
138154
prompt "TX Ready Work Queue"
139155
default CAN_TXREADY_HIPRI

drivers/can/can.c

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,48 @@ static int can_xmit(FAR struct can_dev_s *dev)
595595
}
596596
}
597597

598+
/* When the hardware transmit buffer, not H/W FIFO, is full and
599+
* there are frames in the tx_pending list.
600+
*
601+
* the cancel logic requires hardware transmit buffer must have ability
602+
* of Canceling the transmission of frames.
603+
*
604+
* The can_txneed_cancel function checks whether the ID of the first
605+
* frame in the tx_pending list is smaller than the minimum ID in the
606+
* tx_sending list.
607+
*
608+
* If this condition is met, the can_cancel_mbmsg function is invoked
609+
* to attempt to cancel the transmission of the frame with the largest
610+
* ID in the tx_sending list, and this frame with the largest ID in the
611+
* tx_sending list is then reinserted into the tx_pending list at a
612+
* specified position, can_cancel_mbmsg return true if cancel succeed.
613+
*
614+
* Afterwards, dev_send is called to load the first frame(minimum ID)
615+
* from the tx_pending list into the hardware transmit buffer. make
616+
* this frame with the minimum ID appear on the bus in real time.
617+
*/
618+
619+
#ifdef CONFIG_CAN_TXCANCEL
620+
if (TX_PENDING(&dev->cd_sender) && can_txneed_cancel(&dev->cd_sender))
621+
{
622+
DEBUGASSERT(dev->cd_ops->co_cancel != NULL);
623+
624+
if (can_cancel_mbmsg(dev))
625+
{
626+
msg = can_get_msg(&dev->cd_sender);
627+
628+
/* Send the next message at the sender */
629+
630+
ret = dev_send(dev, msg);
631+
if (ret < 0)
632+
{
633+
canerr("dev_send failed: %d\n", ret);
634+
can_revert_msg(&dev->cd_sender, msg);
635+
}
636+
}
637+
}
638+
#endif
639+
598640
/* Make sure that TX interrupts are enabled */
599641

600642
dev_txint(dev, true);
@@ -627,13 +669,18 @@ static ssize_t can_write(FAR struct file *filep, FAR const char *buffer,
627669

628670
flags = enter_critical_section();
629671

630-
/* Check if the H/W TX is inactive when we started. In certain race
672+
/* if CONFIG_CAN_TXCANCEL is enable, inactive will be always true, else
673+
* Check if the H/W TX is inactive when we started. In certain race
631674
* conditions, there may be a pending interrupt to kick things back off,
632675
* but we will be sure here that there is not. That the hardware is IDLE
633676
* and will need to be kick-started.
634677
*/
635678

636-
inactive = dev_txempty(dev);
679+
#ifdef CONFIG_CAN_TXCANCEL
680+
inactive = true;
681+
#else
682+
inactive = dev_txready(dev);
683+
#endif
637684

638685
/* Add the messages to the sender. Ignore any trailing messages that are
639686
* shorter than the minimum.
@@ -701,7 +748,11 @@ static ssize_t can_write(FAR struct file *filep, FAR const char *buffer,
701748

702749
/* Re-check the H/W sender state */
703750

704-
inactive = dev_txempty(dev);
751+
#ifdef CONFIG_CAN_TXCANCEL
752+
inactive = true;
753+
#else
754+
inactive = dev_txready(dev);
755+
#endif
705756
}
706757

707758
/* We get here if there is space in sender. Add the new

drivers/can/can_sender.c

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,87 @@ void can_send_done(FAR struct can_txcache_s *cd_sender)
245245
}
246246
#endif
247247
}
248+
249+
/****************************************************************************
250+
* Name: can_txneed_cancel
251+
*
252+
* Description:
253+
* Compare the msgID between tx_sending and tx_pending's head when
254+
* dev_txready return false and tx_pending is not empty, preserve the node
255+
* with largest msgID in tx_sending into *callbackmsg_node. return true if
256+
* the msgID in tx_pending's head < the smallest msgID in tx_sending.
257+
*
258+
****************************************************************************/
259+
260+
#ifdef CONFIG_CAN_TXCANCEL
261+
bool can_txneed_cancel(FAR struct can_txcache_s *cd_sender)
262+
{
263+
FAR struct can_msg_node_s *msg_node;
264+
FAR struct can_msg_node_s *tmp_node;
265+
266+
/* acquire min msgID from tx_sending and compare it with masgID
267+
* in tx_pending list's head.
268+
*/
269+
270+
if (SENDING_COUNT(cd_sender) == 0)
271+
{
272+
return false;
273+
}
274+
275+
msg_node = list_first_entry(&cd_sender->tx_pending,
276+
struct can_msg_node_s, list);
277+
tmp_node = list_first_entry(&cd_sender->tx_sending,
278+
struct can_msg_node_s, list);
279+
if (msg_node->msg.cm_hdr.ch_id < tmp_node->msg.cm_hdr.ch_id)
280+
{
281+
return true;
282+
}
283+
284+
return false;
285+
}
286+
#endif
287+
288+
/****************************************************************************
289+
* Name: can_cancel_mbmsg
290+
*
291+
* Description:
292+
* cancel the msg with the largest msgID in the mailbox and
293+
* return true if success.
294+
*
295+
****************************************************************************/
296+
297+
#ifdef CONFIG_CAN_TXCANCEL
298+
bool can_cancel_mbmsg(FAR struct can_dev_s *dev)
299+
{
300+
FAR struct can_msg_node_s *tmp_node;
301+
FAR struct can_msg_node_s *callbackmsg_node;
302+
FAR struct can_txcache_s *cd_sender = &dev->cd_sender;
303+
304+
callbackmsg_node = list_last_entry(&cd_sender->tx_sending,
305+
struct can_msg_node_s, list);
306+
if (dev_cancel(dev, &callbackmsg_node->msg))
307+
{
308+
/* take tx_sending's specific msg back into tx_pending at a
309+
* specified position.
310+
*/
311+
312+
list_delete(&callbackmsg_node->list);
313+
314+
list_for_every_entry(&cd_sender->tx_pending, tmp_node,
315+
struct can_msg_node_s, list)
316+
{
317+
if (tmp_node->msg.cm_hdr.ch_id >=
318+
callbackmsg_node->msg.cm_hdr.ch_id)
319+
{
320+
break;
321+
}
322+
}
323+
324+
list_add_before(&tmp_node->list, &callbackmsg_node->list);
325+
326+
return true;
327+
}
328+
329+
return false;
330+
}
331+
#endif

include/nuttx/can/can.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@
396396
#define dev_send(dev,m) (dev)->cd_ops->co_send(dev,m)
397397
#define dev_txready(dev) (dev)->cd_ops->co_txready(dev)
398398
#define dev_txempty(dev) (dev)->cd_ops->co_txempty(dev)
399+
#define dev_cancel(dev,m) (dev)->cd_ops->co_cancel(dev,m)
399400

400401
/* CAN message support ******************************************************/
401402

@@ -815,6 +816,9 @@ struct can_ops_s
815816
*/
816817

817818
CODE bool (*co_txempty)(FAR struct can_dev_s *dev);
819+
820+
CODE bool (*co_cancel)(FAR struct can_dev_s *dev,
821+
FAR struct can_msg_s *msg);
818822
};
819823

820824
/* This is the device structure used by the driver. The caller of

include/nuttx/can/can_sender.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,5 +235,33 @@ void can_revert_msg(FAR struct can_txcache_s *cd_sender,
235235

236236
void can_send_done(FAR struct can_txcache_s *cd_sender);
237237

238+
/****************************************************************************
239+
* Name: can_txneed_cancel
240+
*
241+
* Description:
242+
* Compare the msgID between tx_sending and tx_pending's head when
243+
* dev_txready return false and tx_pending is not empty, preserve the node
244+
* with largest msgID in tx_sending into *callbackmsg_node. return true if
245+
* the msgID in tx_pending's head < the smallest msgID in tx_sending.
246+
*
247+
****************************************************************************/
248+
249+
#ifdef CONFIG_CAN_TXCANCEL
250+
bool can_txneed_cancel(FAR struct can_txcache_s *cd_sender);
251+
#endif
252+
253+
/****************************************************************************
254+
* Name: can_cancel_mbmsg
255+
*
256+
* Description:
257+
* cancel the msg with the largest msgID in the mailbox and
258+
* return true if success.
259+
*
260+
****************************************************************************/
261+
262+
#ifdef CONFIG_CAN_TXCANCEL
263+
bool can_cancel_mbmsg(FAR struct can_dev_s *dev);
264+
#endif
265+
238266
#endif /* CONFIG_CAN */
239267
#endif /* __INCLUDE_NUTTX_CAN_SENDER_H */

0 commit comments

Comments
 (0)