Skip to content

Conversation

@OceanfromXiaomi
Copy link
Contributor

add the logic about "cancel mechanism" of sending can/canfd message based priority list.

Summary

This PR is intended to resolve the CAN message priority inversion issue that can occur during CAN frame transmission.
It fundamentally addresses the problem of CAN message priority inversion, thereby avoiding communication latency or errors caused by priority inversion.

The solution manages CAN message transmission by introducing a cancel mechanism combined with a linked list, along with predefined cancel conditions (can_txneed_cancel), to control and reorder CAN frame transmission as needed.

Impact

If users want to use this scheme, they only need to enable CONFIG_CAN_TXCANCEL.
This scheme does not affect the build process, security, or compatibility.

A new dev_cancel interface is introduced, which is used to cancel the transmission of CAN messages that are already queued in the CAN driver’s hardware transmit buffer.

Testing

This scheme has been applied in an automotive basic-software project and has undergone long-term testing and validation; it does not break existing code.
The scheme also ran stably on development boards based on the Cortex-M7 architecture.

Test procedure:

  1. Enable CONFIG_CAN_TXCANCEL.
  2. Configure the CAN driver to use only one hardware transmit buffer.
  3. Disconnect the CAN bus.
  4. Using the cansend utility, first send a low-priority CAN message (ID 0x222). After confirming that this message has been placed into the hardware transmit buffer, send a high-priority CAN message (ID 0x111).
  5. Reconnect the CAN bus.

Observed behavior:
the high-priority message (ID 0x111) appears on the bus first, followed by the low-priority message (ID 0x222).

github_PR_1 github_PR_2 github_PR_3

@github-actions github-actions bot added Area: Drivers Drivers issues Size: M The size of the change in this PR is medium labels Dec 26, 2025
@xiaoxiang781216
Copy link
Contributor

@OceanfromXiaomi fix:

../nuttx/tools/checkpatch.sh -c -u -m -g 4a069358b6cbd7acf377f1e32e359c480a5a9679..HEAD
❌ Missing git commit message
Used config files:
    1: .codespellrc
/home/runner/work/nuttx/nuttx/nuttx/drivers/can/can_sender.c:309: specfic ==> specific
Some checks failed. For contributing guidelines, see:

}
}

#ifdef CONFIG_CAN_TXCANCEL
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@OceanfromXiaomi please include some comments explaining the idea, it will help someone looking this code in the future

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK


CODE bool (*co_txempty)(FAR struct can_dev_s *dev);

CODE bool (*co_cancel)(FAR struct can_dev_s *dev,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this deserves some comment in the code. I suppose cancel should abort the msg that is passed as an argument, but should it also reorganize hardware buffers sending order if they are sent to the bus in FIFO order?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not use cancel ability if lower driver enable H/W FIFO to store msg.
I will add explaination in Kconfig file.

depends on CAN_TXPRIORITY
---help---
Enabling this feature adds support for the can cancel ability.
this ability can cancel the msg with the largest msgID in the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix indentation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

callbackmsg_node = list_last_entry(&cd_sender->tx_sending,
struct can_msg_node_s, list);
if (dev->cd_ops->co_cancel != NULL &&
dev_cancel(dev, &callbackmsg_node->msg))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add assert above the statement ensuring the lower half actually has dev_cancel to simplify subsequent debugging.

Or just return false as the lower half is not capable of aborting the frame. I think that's even better than assertion.

Copy link
Contributor Author

@OceanfromXiaomi OceanfromXiaomi Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will do these as follow:

DEBUGASSERT(dev->cd_ops->co_cancel != NULL)
...
if (dev_cancel(dev, &callbackmsg_node->msg))
{
 ...
return true;
}
return false;

config CAN_TXCANCEL
bool "Implement tx cancel ability"
default n
depends on CAN_TXPRIORITY
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these have to be separate configuration options? I would say strict TX priority ordering is useless without the ability to abort the frame from hardware buffer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CAN_TXCANCEL deponds on CAN_TXPRIORITY, because cancel ability will update pending list.

I will add explaination to explain cancel ability deponds on that the hardware buffer has ability of aborting frame.

#ifdef CONFIG_CAN_TXCANCEL
if (TX_PENDING(&dev->cd_sender) && can_txneed_cancel(&dev->cd_sender))
{
if (can_cancel_mbmsg(dev))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be in a while loop? What if you need to cancel more messages? Let's say there are two CAN frames with IDs 1 and 2 already stored in HW buffers and you want to add CAN frame with ID 0. The current design, as I understand it, would have to abort both of these frames.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just cancel the msg with the max id in sending list.
In the current design, when the hardware transmit buffer is full and there are frames in the pending list, the can_txneed_cancel function checks whether the ID of the first frame in the pending list is smaller than the minimum ID in the 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 sending list, and this frame is then reinserted into the pending list at a specified position.

Afterwards, dev_send is called to load the first frame from the pending list into the hardware transmit buffer.

}

#ifdef CONFIG_CAN_TXCANCEL
if (TX_PENDING(&dev->cd_sender) && can_txneed_cancel(&dev->cd_sender))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to cancel the messages unless hardware buffers are actually full, we just need to reorganize the order in which they are sent to the bus (not possible with all CAN controllers, but same can do it). If I am not mistaken, this approach could lead to the following scenario:

  • write CAN frame with ID 5 to first HW buffer
  • write CAN frame with ID 0, we check for abort
  • CAN frame with ID 5 is aborted and returned back to the software FIFO
  • CAN frame 0 is inserted to first HW buffer
  • CAN frame 5 is inserted to second HW buffer

The ideal scenario would be something like this

  • write CAN frame with ID 5 to first HW buffer
  • write CAN frame with ID 0 to second HW buffer
  • change the order of HW buffers so the HW buffer 2 is sent before HW buffer 1

We avoid a lot of list operations and unnecessary aborts. This becomes even more significant if more hardware buffers are used and you need to abort more frames. Yes, the implementation is more complicated and it passes some of the logic to the lower half, because dev_send has to check the priority of the newly added frame and correctly change the order in which the buffers are sent to the bus. Also the cancel has to be handled a bit differently, because now it's the lower half that decides what frame is returned back to the software FIFO.

I am not forcing the change here as this is a lot of work to do, just passing some thoughts and ideas about possible future enhancements. We used this approach in RTEMS CAN stack in my diploma thesis actually -> multiple priority queues, lower half reorganizes the sending order and returns the frame back to SW FIFO. This is something I'd like to see in NuttX in the future as well. It's probably a bit different from your needs, because you are doing strict CAN ID ordering. The problem with that is that some protocols need to keep the sending order even for different identifiers, that's where you need multiple priority classes... you filter ID ranges to these classes and then ensures the highest priority class is sent first, but the order within one class is still kept.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change does not introduce excessive linked-list operations or unnecessary abort operations.

The reasons are as follows:

The prerequisite of this PR is the following loop:
while (TX_PENDING(&dev->cd_sender) && dev_txready(dev))

When this loop finishes, it means that either the hardware transmit buffer is full, there are no more frames in the pending list, or an error occurred during dev_send.

After that, the cancel condition is evaluated:

if (TX_PENDING(&dev->cd_sender) && can_txneed_cancel(&dev->cd_sender))

Only when there are still pending frames in the pending list and the can_txneed_cancel condition is satisfied will the cancel logic be executed.

Therefore, the cancel mechanism is triggered only when necessary, avoiding excessive list operations and unnecessary abort actions.

@OceanfromXiaomi OceanfromXiaomi force-pushed the send_msg_cancel_logic branch 3 times, most recently from 63c1bf3 to aef5984 Compare January 4, 2026 08:58
@OceanfromXiaomi
Copy link
Contributor Author

@OceanfromXiaomi fix:

../nuttx/tools/checkpatch.sh -c -u -m -g 4a069358b6cbd7acf377f1e32e359c480a5a9679..HEAD
❌ Missing git commit message
Used config files:
    1: .codespellrc
/home/runner/work/nuttx/nuttx/nuttx/drivers/can/can_sender.c:309: specfic ==> specific
Some checks failed. For contributing guidelines, see:

Done

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>
Copy link
Contributor

@acassis acassis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@OceanfromXiaomi please update the Documentation: https://nuttx.apache.org/docs/latest/components/drivers/character/can.html to include this new functionality

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Drivers Drivers issues Size: M The size of the change in this PR is medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants