This is an automated email from the ASF dual-hosted git repository.

xiaoxiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new 2a60b7e7471 can: strict TX priority ordering to avoid priority 
inversion
2a60b7e7471 is described below

commit 2a60b7e747138b2a218e6408b8539d7377717895
Author: zhaohaiyang1 <[email protected]>
AuthorDate: Mon Oct 14 13:47:07 2024 +0800

    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 <[email protected]>
---
 Documentation/components/drivers/character/can.rst | 35 ++++++++
 drivers/can/Kconfig                                | 21 ++++-
 drivers/can/can.c                                  | 63 ++++++++++++--
 drivers/can/can_sender.c                           | 96 ++++++++++++++++++++--
 include/nuttx/can/can.h                            |  8 +-
 include/nuttx/can/can_sender.h                     | 32 +++++++-
 6 files changed, 237 insertions(+), 18 deletions(-)

diff --git a/Documentation/components/drivers/character/can.rst 
b/Documentation/components/drivers/character/can.rst
index d5351c475bd..76ddfda7ed3 100644
--- a/Documentation/components/drivers/character/can.rst
+++ b/Documentation/components/drivers/character/can.rst
@@ -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>`_.
diff --git a/drivers/can/Kconfig b/drivers/can/Kconfig
index 402b015c8c7..8dfa5bb6866 100644
--- a/drivers/can/Kconfig
+++ b/drivers/can/Kconfig
@@ -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"
diff --git a/drivers/can/can.c b/drivers/can/can.c
index ef69a92d3d3..d7d7f324237 100644
--- a/drivers/can/can.c
+++ b/drivers/can/can.c
@@ -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
diff --git a/drivers/can/can_sender.c b/drivers/can/can_sender.c
index a2ddc397cde..666fbd07b41 100644
--- a/drivers/can/can_sender.c
+++ b/drivers/can/can_sender.c
@@ -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
\ No newline at end of file
diff --git a/include/nuttx/can/can.h b/include/nuttx/can/can.h
index 517314c930a..03929965cbc 100644
--- a/include/nuttx/can/can.h
+++ b/include/nuttx/can/can.h
@@ -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
diff --git a/include/nuttx/can/can_sender.h b/include/nuttx/can/can_sender.h
index 706aa666edb..976fb0c1e84 100644
--- a/include/nuttx/can/can_sender.h
+++ b/include/nuttx/can/can_sender.h
@@ -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 */

Reply via email to