mirror of
https://github.com/apache/nuttx.git
synced 2026-05-23 23:28:29 +08:00
can: strict TX priority ordering to avoid priority inversion
Introduce a single configuration option to provide strict transmit priority ordering based on CAN ID. The intention is to expose the user-visible behavior (strict TX priority ordering) rather than the underlying implementation details. Strict priority ordering is only meaningful when hardware transmit buffer cancellation is available, so splitting this functionality into separate configuration options was misleading and could result in partially effective or incorrect configurations. Signed-off-by: zhaohaiyang1 <zhaohaiyang1@xiaomi.com>
This commit is contained in:
@@ -69,6 +69,41 @@ The upper half driver supports the following ``ioctl`` commands:
|
||||
- **CANIOC_GET_MSGALIGN**: Get messages alignment. See CANIOC_SET_MSGALIGN for
|
||||
explanation.
|
||||
|
||||
The upper half driver supports the **strict TX priority ordering**:
|
||||
|
||||
- When the CAN controller hardware supports cancelling an ongoing transmission
|
||||
from a hardware transmit buffer, the following transmit-cancel mechanism can
|
||||
be used to avoid priority inversion when all hardware transmit buffers are
|
||||
full.
|
||||
|
||||
- **Behavior**: When the hardware transmit buffers are full and there are
|
||||
frames queued in the software tx_pending list, the driver compares the
|
||||
highest-priority frame in the software tx_pending list with the
|
||||
highest-priority frame currently resident in hardware. If the highest-priority
|
||||
pending frame has a higher priority than the highest-priority hardware-resident
|
||||
frame, the driver will:
|
||||
|
||||
- Cancel the transmission of the lowest-priority frame currently in the hardware
|
||||
transmit buffers (the controller must support cancellation).
|
||||
- Reinsert that cancelled frame back into the software tx_pending list at the
|
||||
appropriate position.
|
||||
- Fill the freed hardware transmit buffer with the higher-priority frame taken
|
||||
from the software tx_pending list.
|
||||
|
||||
This mechanism helps prevent priority inversion when all hardware transmit buffers
|
||||
are full, by ensuring that the highest-priority frame is always transmitted first.
|
||||
|
||||
- **Note**: The "hardware transmit buffer" in this context refers to individual H/W
|
||||
transmit message buffers and not to a hardware FIFO.
|
||||
|
||||
- **Requirements**:
|
||||
- The CAN controller must support cancellation of an ongoing buffered transmission.
|
||||
|
||||
- The driver implementation (upper half / lower half) must cooperate with the cancel
|
||||
operation and correctly manage the tx_pending and tx_sending lists.
|
||||
|
||||
- The feature should be enabled via a configuration option(``CONFIG_CAN_STRICT_TX_PRIORITY``).
|
||||
|
||||
**Usage Note**: The default behavior of the upper half driver is to return
|
||||
multiple messages on ``read``. See the `guide on this subject
|
||||
</guides/reading_can_msgs.html>`_.
|
||||
|
||||
+19
-2
@@ -128,11 +128,28 @@ config CAN_TXREADY
|
||||
no longer full. can_txready() will then awaken the
|
||||
can_write() logic and the hang condition is avoided.
|
||||
|
||||
config CAN_TXPRIORITY
|
||||
config CAN_STRICT_TX_PRIORITY
|
||||
bool "Prioritize sending based on canid"
|
||||
default n
|
||||
---help---
|
||||
Prioritize sending based on canid.
|
||||
Enable strict transmit priority ordering based on CAN ID.
|
||||
|
||||
When the hardware transmit buffers (not a hardware FIFO) are
|
||||
full and there are frames pending in the software tx_pending list,
|
||||
the cancel ability is taken effect. this feature may cancel
|
||||
the msg with the largest CAN ID in the mailbox.
|
||||
|
||||
Specifically, the driver may cancel the transmission of the
|
||||
lowest-priority frame(the msg with the largest CAN ID) currently
|
||||
stored in a hardware transmit buffer, reinsert that frame back
|
||||
into the software pending list, and replace it with a
|
||||
higher-priority pending frame.
|
||||
|
||||
This feature requires that the CAN controller hardware supports
|
||||
cancellation (abort) of buffered transmissions.
|
||||
|
||||
Do not enable this option if the lower-half driver uses a hardware
|
||||
FIFO to store transmit frames.
|
||||
|
||||
choice
|
||||
prompt "TX Ready Work Queue"
|
||||
|
||||
+57
-6
@@ -595,6 +595,48 @@ static int can_xmit(FAR struct can_dev_s *dev)
|
||||
}
|
||||
}
|
||||
|
||||
/* When the hardware transmit buffer, not H/W FIFO, is full and
|
||||
* there are frames in the tx_pending list.
|
||||
*
|
||||
* the cancel logic requires hardware transmit buffer must have ability
|
||||
* of Canceling the transmission of frames.
|
||||
*
|
||||
* The can_txneed_cancel function checks whether the ID of the first
|
||||
* frame in the tx_pending list is smaller than the minimum ID in the
|
||||
* tx_sending list.
|
||||
*
|
||||
* If this condition is met, the can_cancel_mbmsg function is invoked
|
||||
* to attempt to cancel the transmission of the frame with the largest
|
||||
* ID in the tx_sending list, and this frame with the largest ID in the
|
||||
* tx_sending list is then reinserted into the tx_pending list at a
|
||||
* specified position, can_cancel_mbmsg return true if cancel succeed.
|
||||
*
|
||||
* Afterwards, dev_send is called to load the first frame(minimum ID)
|
||||
* from the tx_pending list into the hardware transmit buffer. make
|
||||
* this frame with the minimum ID appear on the bus in real time.
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_CAN_STRICT_TX_PRIORITY
|
||||
if (TX_PENDING(&dev->cd_sender) && can_txneed_cancel(&dev->cd_sender))
|
||||
{
|
||||
DEBUGASSERT(dev->cd_ops->co_cancel != NULL);
|
||||
|
||||
if (can_cancel_mbmsg(dev))
|
||||
{
|
||||
msg = can_get_msg(&dev->cd_sender);
|
||||
|
||||
/* Send the next message at the sender */
|
||||
|
||||
ret = dev_send(dev, msg);
|
||||
if (ret < 0)
|
||||
{
|
||||
canerr("dev_send failed: %d\n", ret);
|
||||
can_revert_msg(&dev->cd_sender, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Make sure that TX interrupts are enabled */
|
||||
|
||||
dev_txint(dev, true);
|
||||
@@ -627,13 +669,18 @@ static ssize_t can_write(FAR struct file *filep, FAR const char *buffer,
|
||||
|
||||
flags = enter_critical_section();
|
||||
|
||||
/* Check if the H/W TX is inactive when we started. In certain race
|
||||
* conditions, there may be a pending interrupt to kick things back off,
|
||||
* but we will be sure here that there is not. That the hardware is IDLE
|
||||
* and will need to be kick-started.
|
||||
/* if CONFIG_CAN_STRICT_TX_PRIORITY is enable, inactive will be always
|
||||
* true, else check if the H/W TX is inactive when we started. In certain
|
||||
* race conditions, there may be a pending interrupt to kick things back
|
||||
* off, but we will be sure here that there is not. That the hardware
|
||||
* is IDLE and will need to be kick-started.
|
||||
*/
|
||||
|
||||
inactive = dev_txempty(dev);
|
||||
#ifdef CONFIG_CAN_STRICT_TX_PRIORITY
|
||||
inactive = true;
|
||||
#else
|
||||
inactive = dev_txready(dev);
|
||||
#endif
|
||||
|
||||
/* Add the messages to the sender. Ignore any trailing messages that are
|
||||
* shorter than the minimum.
|
||||
@@ -701,7 +748,11 @@ static ssize_t can_write(FAR struct file *filep, FAR const char *buffer,
|
||||
|
||||
/* Re-check the H/W sender state */
|
||||
|
||||
inactive = dev_txempty(dev);
|
||||
#ifdef CONFIG_CAN_STRICT_TX_PRIORITY
|
||||
inactive = true;
|
||||
#else
|
||||
inactive = dev_txready(dev);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* We get here if there is space in sender. Add the new
|
||||
|
||||
@@ -41,11 +41,11 @@
|
||||
|
||||
void can_sender_init(FAR struct can_txcache_s *cd_sender)
|
||||
{
|
||||
#if defined(CONFIG_CAN_TXPRIORITY) && CONFIG_CAN_TXFIFOSIZE <= 0
|
||||
#if defined(CONFIG_CAN_STRICT_TX_PRIORITY) && CONFIG_CAN_TXFIFOSIZE <= 0
|
||||
# error "CONFIG_CAN_TXFIFOSIZE should be positive non-zero value"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_CAN_TXPRIORITY
|
||||
#ifdef CONFIG_CAN_STRICT_TX_PRIORITY
|
||||
int i;
|
||||
|
||||
list_initialize(&cd_sender->tx_free);
|
||||
@@ -75,7 +75,7 @@ void can_sender_init(FAR struct can_txcache_s *cd_sender)
|
||||
void can_add_sendnode(FAR struct can_txcache_s *cd_sender,
|
||||
FAR struct can_msg_s *msg, int msglen)
|
||||
{
|
||||
#ifdef CONFIG_CAN_TXPRIORITY
|
||||
#ifdef CONFIG_CAN_STRICT_TX_PRIORITY
|
||||
FAR struct list_node *node;
|
||||
FAR struct can_msg_node_s *msg_node;
|
||||
FAR struct can_msg_node_s *tmp_node;
|
||||
@@ -130,7 +130,7 @@ FAR struct can_msg_s *can_get_msg(FAR struct can_txcache_s *cd_sender)
|
||||
{
|
||||
FAR struct can_msg_s *msg = NULL;
|
||||
|
||||
#ifdef CONFIG_CAN_TXPRIORITY
|
||||
#ifdef CONFIG_CAN_STRICT_TX_PRIORITY
|
||||
FAR struct can_msg_node_s *msg_node;
|
||||
FAR struct can_msg_node_s *tmp_node;
|
||||
|
||||
@@ -198,7 +198,7 @@ FAR struct can_msg_s *can_get_msg(FAR struct can_txcache_s *cd_sender)
|
||||
void can_revert_msg(FAR struct can_txcache_s *cd_sender,
|
||||
FAR struct can_msg_s *msg)
|
||||
{
|
||||
#ifdef CONFIG_CAN_TXPRIORITY
|
||||
#ifdef CONFIG_CAN_STRICT_TX_PRIORITY
|
||||
FAR struct can_msg_node_s *msg_node;
|
||||
|
||||
msg_node = container_of(msg, struct can_msg_node_s, msg);
|
||||
@@ -230,7 +230,7 @@ void can_revert_msg(FAR struct can_txcache_s *cd_sender,
|
||||
|
||||
void can_send_done(FAR struct can_txcache_s *cd_sender)
|
||||
{
|
||||
#ifdef CONFIG_CAN_TXPRIORITY
|
||||
#ifdef CONFIG_CAN_STRICT_TX_PRIORITY
|
||||
FAR struct list_node *node;
|
||||
|
||||
node = list_remove_head(&cd_sender->tx_sending);
|
||||
@@ -245,3 +245,87 @@ void can_send_done(FAR struct can_txcache_s *cd_sender)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: can_txneed_cancel
|
||||
*
|
||||
* Description:
|
||||
* Compare the msgID between tx_sending and tx_pending's head when
|
||||
* dev_txready return false and tx_pending is not empty, preserve the node
|
||||
* with largest msgID in tx_sending into *callbackmsg_node. return true if
|
||||
* the msgID in tx_pending's head < the smallest msgID in tx_sending.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef CONFIG_CAN_STRICT_TX_PRIORITY
|
||||
bool can_txneed_cancel(FAR struct can_txcache_s *cd_sender)
|
||||
{
|
||||
FAR struct can_msg_node_s *msg_node;
|
||||
FAR struct can_msg_node_s *tmp_node;
|
||||
|
||||
/* acquire min msgID from tx_sending and compare it with masgID
|
||||
* in tx_pending list's head.
|
||||
*/
|
||||
|
||||
if (SENDING_COUNT(cd_sender) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
msg_node = list_first_entry(&cd_sender->tx_pending,
|
||||
struct can_msg_node_s, list);
|
||||
tmp_node = list_first_entry(&cd_sender->tx_sending,
|
||||
struct can_msg_node_s, list);
|
||||
if (msg_node->msg.cm_hdr.ch_id < tmp_node->msg.cm_hdr.ch_id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Name: can_cancel_mbmsg
|
||||
*
|
||||
* Description:
|
||||
* cancel the msg with the largest msgID in the mailbox and
|
||||
* return true if success.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef CONFIG_CAN_STRICT_TX_PRIORITY
|
||||
bool can_cancel_mbmsg(FAR struct can_dev_s *dev)
|
||||
{
|
||||
FAR struct can_msg_node_s *tmp_node;
|
||||
FAR struct can_msg_node_s *callbackmsg_node;
|
||||
FAR struct can_txcache_s *cd_sender = &dev->cd_sender;
|
||||
|
||||
callbackmsg_node = list_last_entry(&cd_sender->tx_sending,
|
||||
struct can_msg_node_s, list);
|
||||
if (dev_cancel(dev, &callbackmsg_node->msg))
|
||||
{
|
||||
/* take tx_sending's specific msg back into tx_pending at a
|
||||
* specified position.
|
||||
*/
|
||||
|
||||
list_delete(&callbackmsg_node->list);
|
||||
|
||||
list_for_every_entry(&cd_sender->tx_pending, tmp_node,
|
||||
struct can_msg_node_s, list)
|
||||
{
|
||||
if (tmp_node->msg.cm_hdr.ch_id >=
|
||||
callbackmsg_node->msg.cm_hdr.ch_id)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
list_add_before(&tmp_node->list, &callbackmsg_node->list);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
@@ -396,6 +396,7 @@
|
||||
#define dev_send(dev,m) (dev)->cd_ops->co_send(dev,m)
|
||||
#define dev_txready(dev) (dev)->cd_ops->co_txready(dev)
|
||||
#define dev_txempty(dev) (dev)->cd_ops->co_txempty(dev)
|
||||
#define dev_cancel(dev,m) (dev)->cd_ops->co_cancel(dev,m)
|
||||
|
||||
/* CAN message support ******************************************************/
|
||||
|
||||
@@ -677,7 +678,7 @@ struct can_rxfifo_s
|
||||
struct can_msg_s rx_buffer[CONFIG_CAN_RXFIFOSIZE];
|
||||
};
|
||||
|
||||
#ifdef CONFIG_CAN_TXPRIORITY
|
||||
#ifdef CONFIG_CAN_STRICT_TX_PRIORITY
|
||||
struct can_msg_node_s
|
||||
{
|
||||
struct list_node list;
|
||||
@@ -688,7 +689,7 @@ struct can_msg_node_s
|
||||
struct can_txcache_s
|
||||
{
|
||||
sem_t tx_sem; /* Counting semaphore */
|
||||
#ifdef CONFIG_CAN_TXPRIORITY
|
||||
#ifdef CONFIG_CAN_STRICT_TX_PRIORITY
|
||||
/* tx_buffer - Buffer of CAN message. And this buffer is managed by
|
||||
* tx_free/tx_pending/tx_sending
|
||||
* tx_free - Link all buffer node in the initial step
|
||||
@@ -815,6 +816,9 @@ struct can_ops_s
|
||||
*/
|
||||
|
||||
CODE bool (*co_txempty)(FAR struct can_dev_s *dev);
|
||||
|
||||
CODE bool (*co_cancel)(FAR struct can_dev_s *dev,
|
||||
FAR struct can_msg_s *msg);
|
||||
};
|
||||
|
||||
/* This is the device structure used by the driver. The caller of
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef CONFIG_CAN_TXPRIORITY
|
||||
#ifdef CONFIG_CAN_STRICT_TX_PRIORITY
|
||||
|
||||
/* There are three linked lists to manage TX buffer:
|
||||
* tx_free - can_write function get a tx_free node, write message, then
|
||||
@@ -145,7 +145,7 @@
|
||||
% CONFIG_CAN_TXFIFOSIZE; \
|
||||
}) \
|
||||
|
||||
#endif /* CONFIG_CAN_TXPRIORITY */
|
||||
#endif /* CONFIG_CAN_STRICT_TX_PRIORITY */
|
||||
|
||||
/****************************************************************************
|
||||
* Public Function Prototypes
|
||||
@@ -235,5 +235,33 @@ void can_revert_msg(FAR struct can_txcache_s *cd_sender,
|
||||
|
||||
void can_send_done(FAR struct can_txcache_s *cd_sender);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: can_txneed_cancel
|
||||
*
|
||||
* Description:
|
||||
* Compare the msgID between tx_sending and tx_pending's head when
|
||||
* dev_txready return false and tx_pending is not empty, preserve the node
|
||||
* with largest msgID in tx_sending into *callbackmsg_node. return true if
|
||||
* the msgID in tx_pending's head < the smallest msgID in tx_sending.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef CONFIG_CAN_STRICT_TX_PRIORITY
|
||||
bool can_txneed_cancel(FAR struct can_txcache_s *cd_sender);
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Name: can_cancel_mbmsg
|
||||
*
|
||||
* Description:
|
||||
* cancel the msg with the largest msgID in the mailbox and
|
||||
* return true if success.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef CONFIG_CAN_STRICT_TX_PRIORITY
|
||||
bool can_cancel_mbmsg(FAR struct can_dev_s *dev);
|
||||
#endif
|
||||
|
||||
#endif /* CONFIG_CAN */
|
||||
#endif /* __INCLUDE_NUTTX_CAN_SENDER_H */
|
||||
|
||||
Reference in New Issue
Block a user