CAN Driver Implementation Guidelines
************************************

   **Target audience:** Developers who need to write a custom CAN driver
   for CC27XX devices that are affected by the **SYS‑211** advisory.
   **Goal:** Summarize the mandatory changes, explain *why* they are
   required, and provide ready‑to‑copy code snippets taken from the TI
   reference driver (**CANCC27XXX10.c**).


Background – What is SYS‑211?
=============================

SYS‑211 advisory details an issue that affects the CAN controller and
other bus initiators on CC274XX-Q1 silicon. When a write is performed to
CAN registers while other bus initiators (DMA, HSM, UART, SPI, I2S) are
active, the CAN controller can ignore the **HREADYIN** control signal
and generate back‑to‑back internal accesses with random data. This
results in undefined behavior when it occurs on certain critical CAN
registers.

**CAN Driver Workaround** (as defined by TI):

-  All writes to the *critical* CAN registers must be performed via DMA
   while **all other bus initiators are disabled**.
-  The DMA write routine must block on a semaphore, therefore it
   **cannot be called from an ISR**.
-  Interrupt handling must be deferred to a task/thread.
-  Any driver‑library function that writes a critical register must be
   replaced by the DMA‑based version.
-  Access to the CAN write functions must be protected with a semaphore.

The reference implementation that follows these rules is located in the
SDK under **/source/ti/can/drivers/CANCC27XXX10.c**.


Summary of Key Requirements
===========================

To properly implement a CAN driver on silicon affected by SYS‑211:

-  **Never write a critical CAN register directly.** Use the DMA helper
   (``CANCC27XX_writeRegDma``).
-  **Never call the DMA helper from an ISR.** Defer the ISR to a task
   using a semaphore.
-  **Wrap any Tx path with a binary semaphore.**
-  **Re‑implement any weak driver‑lib function that writes a critical
   register** so it routes through the DMA helper.
-  **Handle DMA power and resources correctly**: Set dependencies on DMA
   during initialization and release them during cleanup.


Critical CAN Registers
======================

The following registers are *critical* and must be written via DMA
helper:

================================= ==============================
Register                          Description
================================= ==============================
``MCAN_TXBAR``                    Tx Buffer Add Request
``MCAN_TXEFA``                    Tx Event FIFO Acknowledge
``MCAN_RXF0A``                    Rx FIFO0 Acknowledge
``MCAN_RXF1A``                    Rx FIFO1 Acknowledge
``MCAN_TXBCR``                    Tx Buffer Cancellation Request
``MCAN_CCCR``                     CAN Control
``MCAN_NDAT1`` / ``MCAN_NDAT2``   New Data registers
``MCANSS_CLKCTL``                 Clock control
``MCANSS_EOI``                    End‑of‑Interrupt
``CANFD_ICLR0`` / ``CANFD_ICLR1`` Interrupt clear registers
================================= ==============================

These are defined in the driver as an array:

.. code:: c

   /* Array of critical MCAN registers that require special handling due to errata
    * SYS_211.
    */
   static uint32_t CANCC27XX_mcanCriticalRegs[] = {
       /* Most frequently accessed registers are listed first */
       MCAN_TXBAR,
       MCAN_TXEFA,
       MCAN_RXF0A,
       MCAN_RXF1A,
       MCAN_NDAT1,
       MCAN_NDAT2,
       MCAN_CCCR,
       MCAN_TXBCR,
   };

This function searches the array to determine if a register is critical:

.. code:: c

   /*
    *  Returns true if the provided offset matches a critical MCAN register.
    */
   static bool CANCC27XX_isCriticalMcanReg(uint32_t offset)
   {
       bool found = false;
       size_t i;
       size_t numRegs = sizeof(CANCC27XX_mcanCriticalRegs) / sizeof(CANCC27XX_mcanCriticalRegs[0]);

       for (i = 0; i < numRegs; i++)
       {
           if (CANCC27XX_mcanCriticalRegs[i] == offset)
           {
               found = true;
               break;
           }
       }

       return found;
   }



DMA‑Based Register Write
========================

DMA Configuration for Register Writes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Allocate a dedicated DMA channel for critical CAN register writes:

.. code:: c

   #define CANCC27XX_REG_WRITE_DMA_CHANNEL_NUM   10U
   #define CANCC27XX_WRITE_REG_UDMA_CHANNEL_MASK ((uint32_t)1UL << CANCC27XX_REG_WRITE_DMA_CHANNEL_NUM)

   /* DMA Control Table Entries */
   ALLOCATE_CONTROL_TABLE_ENTRY(dmaChannel10ControlTableEntry, CANCC27XX_REG_WRITE_DMA_CHANNEL_NUM);

   static volatile uDMAControlTableEntry *CANCC27XX_regWriteDmaTableEntry = &dmaChannel10ControlTableEntry;

   /* Variable to hold register value to be written by uDMA */
   static uint32_t CANCC27XX_regVal;

DMA‑Based Register Write Helper Function
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

All critical registers are written using DMA via
**``CANCC27XX_writeRegDma()``**. The function does the following:

1. Acquires the *CommonResource* lock (prevents use of other bus
   initiators).
2. Disables interrupts (``PRIMASK = 1``).
3. Programs a one‑shot uDMA channel that copies the value from SRAM to
   the CAN register address.
4. Waits (polling the DMA‑done interrupt) **without touching the bus**.
5. Restores the interrupt mask and releases the *CommonResource* lock.

The following function must be used verbatim without any modifications:

.. code:: c

   /*
    *  This function writes a CAN register using the uDMA after ensuring other bus
    *  masters are inactive and all interrupts are disabled. This function cannot
    *  be called from a context where blocking is not allowed.
    */
   static void CANCC27XX_writeRegDma(uint32_t addr, uint32_t value)
   {
       uint32_t dmaDoneMask;
       uint32_t primask;

       /* Due to SYS_211, acquire lock to arbitrate access to the HSM, CAN,
        * and APU.
        */
       CommonResourceXXF3_acquireLock(SemaphoreP_WAIT_FOREVER);

       /* Store the value in SRAM */
       CANCC27XX_regVal = value;
       
       /* Store current PRIMASK and set PRIMASK=1 to disable interrupts.
        * HwiP_disable() is not used here since it may write BASEPRI which leaves
        * high-priority interrupts enabled.
        */
       primask = __get_PRIMASK();
       __set_PRIMASK(1);

       /* Configure the CAN reg write DMA control structure for a single 32-bit
        * (word-aligned) transaction in auto-request mode with no address
        * incrementing.
        */
       CANCC27XX_regWriteDmaTableEntry->control = (UDMA_SIZE_32 | UDMA_SRC_INC_NONE | UDMA_DST_INC_NONE |
                                                   UDMA_ARB_1 | UDMA_MODE_AUTO);

       /* Set DMA channel to high priority to minimize the critical section duration.
        * It is assumed no other drivers in the system are using this DMA channel and
        * thus we do not need to save and restore the previous priority.
        */
       uDMASetChannelPriority(CANCC27XX_WRITE_REG_UDMA_CHANNEL_MASK);

       /* Configure the DMA transfer source and destination end addresses. The end
        * addresses are the same as the start addresses since address incrementing
        * is disabled in the control struct.
        */
       CANCC27XX_regWriteDmaTableEntry->pSrcEndAddr = (void *)(&CANCC27XX_regVal);
       CANCC27XX_regWriteDmaTableEntry->pDstEndAddr = (void *)(addr);

       /* Make sure all DMA interrupts are disabled except for the channel being
        * used. Save the mask to restore it when the transaction is done.
        */
       dmaDoneMask                      = HWREG(DMA_BASE + DMA_O_DONEMASK);
       HWREG(DMA_BASE + DMA_O_DONEMASK) = CANCC27XX_WRITE_REG_UDMA_CHANNEL_MASK;

       /* Clear potential DMA channel request done for the channel being used */
       uDMAClearInt(CANCC27XX_WRITE_REG_UDMA_CHANNEL_MASK);

       /* Read any DMA register to ensure the DMA channel request done clear above
        * has completed before clearing the DMA interrupt.
        */
       HWREG(DMA_BASE + DMA_O_DONEMASK);

       /* Clear potential pending DMA interrupt */
       HwiP_clearInterrupt(INT_DMA_DONE_COMB);

       /* Enable the DMA channel; it will be disabled automatically by the uDMA
        * controller when the transfer is complete.
        */
       uDMAEnableChannel(CANCC27XX_WRITE_REG_UDMA_CHANNEL_MASK);

       /* Start the uDMA transfer. Due to SYS_211, there cannot be any
        * system bus accesses from this point until the transfer is done.
        */
       uDMARequestChannel(CANCC27XX_WRITE_REG_UDMA_CHANNEL_MASK);

       /* Wait until the uDMA transaction is finished by polling the NVIC interrupt
        * set pending register. This code is written in assembly using only CPU
        * registers to prevent accessing system bus while the DMA transaction is
        * ongoing. The assembly is equivalent to:
        *     while (IntGetPend(INT_DMA_DONE_COMB) == false) {};
        */
       __asm volatile("1:\n\t"                    /* Numerical local label */
                      "ldr r0, =0xE000E200\n\t"   /* Load NVIC ISPR (Interrupt Set-Pending Register) address */
                      "ldr r0, [r0]\n\t"          /* Read NVIC ISPR */
                      "ands.w r0, r0, #0x100\n\t" /* Mask NVIC (1 << (24-16 = 8) -> 0x100) */
                      "beq 1b\n\t"                /* Branch back to label if result is 0 */
                      :                           /* No output operands */
                      :                           /* No input operands */
                      : "r0", "cc", "memory");    /* Clobber list: r0, condition codes, memory */

       /* Clear the DMA channel request done */
       uDMAClearInt(CANCC27XX_WRITE_REG_UDMA_CHANNEL_MASK);

       /* Read any DMA register to ensure the DMA channel request done clear above
        * has completed before clearing the DMA interrupt.
        */
       HWREG(DMA_BASE + DMA_O_DONEMASK);

       /* Clear the pending DMA interrupt */
       HwiP_clearInterrupt(INT_DMA_DONE_COMB);

       /* Read the pending interrupt status to ensure the write above has taken
        * effect before re-enabling interrupts.
        */
       (void)IntGetPend(INT_DMA_DONE_COMB);

       /* Restore DMA interrupt mask */
       HWREG(DMA_BASE + DMA_O_DONEMASK) = dmaDoneMask;

       /* Restore PRIMASK */
       __set_PRIMASK(primask);

       CommonResourceXXF3_releaseLock();
   }

**Important:** This routine **blocks** on the common resource semaphore.
Consequently, it **must never be called from an ISR**.

Register Write Wrapper Function
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Implement a wrapper function for all CAN register writes which will
check for critical registers and write them using DMA-based helper
function.

.. code:: c

   void MCAN_writeReg(uint32_t offset, uint32_t value)
   {
       uint32_t addr;

       /* Determine if the offset is for an MCAN register or message RAM */
       if (offset < CANFD_SRAM_BASE)
       {
           /* MCAN register: add the offset to CAN-FD peripheral base address */
           addr = CANFD_BASE + offset;

           if (CANCC27XX_isCriticalMcanReg(offset))
           {
               /* Due to SYS_211, use DMA to write the register */
               CANCC27XX_writeRegDma(addr, value);
           }
           else
           {
               HWREG(addr) = value;
           }
       }
       else
       {
           /* MCAN message RAM location: directly use the offset as the address */
           addr = offset;

           HWREG(addr) = value;
       }
   }


Deferred Interrupt Handling
===========================

Because the ISR cannot call ``CANCC27XX_writeRegDma()``, the driver
moves the work to a **RTOS task**:

-  The hardware interrupt (``INT_CAN_IRQ``) runs a tiny HWI
   (``CANCC27XX_hwiFxn``) that only disables the interrupt and **posts a
   semaphore**.
-  A dedicated task (``CANCC27XX_taskFxn``) waits on that semaphore,
   then calls the full driver ISR (``CANCC27XX_irqHandler``) where any
   DMA‑based register writes may occur.

.. code:: c

   /* CANCC27XXX10.c – HWI that defers work */
   void CANCC27XX_hwiFxn(uintptr_t arg)
   {
       (void)arg; /* unused arg */

       /* Due to SYS_211, IRQ handling must be deferred to a task to avoid
        * system deadlock, missed interrupts, and priority inversion.
        */

       /* Disable the CAN interrupt until the task can process it */
       IntDisable(INT_CAN_IRQ);

       /* Unblock task to handle the interrupt */
       SemaphoreP_post(&canIrqSemaphore);
   }

   /* CANCC27XXX10.c – task that processes the interrupt */
   static void CANCC27XX_taskFxn(void *arg)
   {
       while (1)
       {
           /* Wait for interrupt handler to post semaphore */
           (void)SemaphoreP_pend(&canIrqSemaphore, SemaphoreP_WAIT_FOREVER);

           /* Call actual interrupt handling function */
           handleCanInterrupt(arg);

           /* Re-enable the CAN interrupt */
           IntEnable(INT_CAN_IRQ);
       }
   }

..

   **Take‑away:** All CAN‑related ISR work (including calls to
   ``CANCC27XX_writeRegDma()``) must be performed in a task context.



Overriding Weak DriverLib Functions
===================================

The TI driver library provides a few *weak* functions that write
directly to critical CAN HW registers: ``CANSetEndOfInt``,
``CANClearInt``, ``CANSSSetClkStopCtrl``, ``CANSSClearClkStopCtrl``). In
the reference driver they are **re‑implemented** to route the write
through ``CANCC27XX_writeRegDma()`` as a workaround for SYS‑211.

.. code:: c

   /* CANCC27XXX10.c - new driver library functions */

   void CANSSSetEndOfInt(uint32_t eoi)
   {
       CANCC27XX_writeRegDma(CANFD_BASE + CANFD_O_MCANSS_EOI, eoi);
   }

   void CANClearInt(uint8_t lineNum, uint32_t flags)
   {
       uint32_t offset = (lineNum == CAN_INT_LINE0) ? CANFD_O_ICLR0 : CANFD_O_ICLR1;

       CANCC27XX_writeRegDma(CANFD_BASE + offset, flags);
   }

   void CANSSSetClkStopCtrl(uint32_t flags)
   {
       CANCC27XX_writeRegDma(CANFD_BASE + CANFD_O_MCANSS_CLKCTL, flags);
   }

   void CANSSClearClkStopCtrl(uint32_t flags)
   {
       CANCC27XX_writeRegDma(CANFD_BASE + CANFD_O_MCANSS_CLKCTL, (uint32_t)~flags);
   }

..

   **Rule:** Any function that ends up writing to a register in the
   critical list must be rewritten to call ``CANCC27XX_writeRegDma()``
   (or a wrapper that does so).


Protecting the Tx Buffer Access
===============================

The Tx path (``CAN_write`` and ``CAN_writeBuffer``) both write to the Tx
buffers and add the Tx request using a DMA‑based register write to
``MCAN_TXBAR``). Therefore, the driver protects the whole operation with
a **binary semaphore** (``CANCC27XX_writeSemaphore``) to ensure
exclusive access between any tasks and ISR.

.. code:: c

   int_fast16_t CAN_write(CAN_Handle handle, const MCAN_TxBufElement *elem)
   {
       CAN_Object *object = handle->object;
       int_fast16_t status = CAN_STATUS_ERROR;
       MCAN_TxFifoQStatus fifoQStatus = {0};

       if (object->txFifoQNum != 0U) {
           /* 1) Grab exclusive access to the Tx buffer */
           SemaphoreP_pend(&CANCC27XX_writeSemaphore, SemaphoreP_WAIT_FOREVER);

           MCAN_getTxFifoQStatus(&fifoQStatus);
           if (fifoQStatus.fifoFull == 0U) {
               MCAN_writeTxMsg(fifoQStatus.putIdx, elem);
               MCAN_setTxBufAddReq(fifoQStatus.putIdx);   /* <-- DMA‑protected register */
               status = CAN_STATUS_SUCCESS;
           } else {
               /* Queue is full – fall back to software ring buffer */
               if (StructRingBuf_put(&object->txStructRingBuf, elem) < 0)
                   status = CAN_STATUS_TX_BUF_FULL;
               else
                   status = CAN_STATUS_SUCCESS;
           }

           /* 2) Release the semaphore */
           SemaphoreP_post(&CANCC27XX_writeSemaphore);
       }
       return status;
   }

   /* CAN_writeBuffer follows the same pattern */
   int_fast16_t CAN_writeBuffer(CAN_Handle handle, uint32_t bufIdx, const MCAN_TxBufElement *elem)
   {
       CAN_Object *object  = handle->object;
       int_fast16_t status = CAN_STATUS_ERROR;
       uint32_t pendingTx;

       if (bufIdx < object->txBufNum)
       {
           /* Ensure exclusive access to the Tx buffer until the Tx request is added.
            * No need to check return value when waiting forever.
            */
           (void)SemaphoreP_pend(&CANCC27XX_writeSemaphore, SemaphoreP_WAIT_FOREVER);

           pendingTx = MCAN_getTxBufReqPend();

           if ((((uint32_t)1U << bufIdx) & pendingTx) == 0U)
           {
               MCAN_writeTxMsg(bufIdx, elem);

               MCAN_setTxBufAddReq(bufIdx);

               status = CAN_STATUS_SUCCESS;
           }

           SemaphoreP_post(&CANCC27XX_writeSemaphore);
       }

       return status;
   }

..

   **Take‑away:** All code that writes to the CAN Tx buffers should be
   protected by a semaphore to ensure exclusive access to the buffer
   until after the transmit request pending register is written via DMA.
   A critical section *cannot* be used as protection since the
   DMA‑protected register write blocks on a semaphore and which could
   result in a dead‑lock if interrupts are disabled.


Driver Initialization and Cleanup
=================================

Initialization
~~~~~~~~~~~~~~

When initializing your CAN driver, ensure you:

1. Initialize the CommonResource access semaphore for SYS_211 advisory
2. Create binary semaphores for interrupt handling and write protection
3. Create a task for handling CAN interrupts
4. Set power dependency on DMA

.. code:: c

   int_fast16_t CANCC27XXXX_init(const CAN_Config *config)
   {
       int_fast16_t status = CAN_STATUS_SUCCESS;
       TaskP_Params taskParams;

       /* Initialize CommonResource access semaphore, needed due to SYS_211 */
       CommonResourceXXF3_constructRTOSObjects();

       /* Create binary semaphore for IRQ handling */
       if (SemaphoreP_constructBinary(&CANCC27XX_irqSemaphore, 0) == NULL)
       {
           status = CAN_STATUS_ERROR;
       }

       if (status == CAN_STATUS_SUCCESS)
       {
           /* Create binary semaphore for CAN_write() */
           if (SemaphoreP_constructBinary(&CANCC27XX_writeSemaphore, 1) == NULL)
           {
               SemaphoreP_destruct(&CANCC27XX_irqSemaphore);
               status = CAN_STATUS_ERROR;
           }
       }

       if (status == CAN_STATUS_SUCCESS)
       {
           /* Initialize task params */
           TaskP_Params_init(&taskParams);
           taskParams.name      = "CANCC27XX";
           taskParams.priority  = CANCC27XXX10_config.taskPri;
           taskParams.stack     = CANCC27XXX10_config.taskStack;
           taskParams.stackSize = CANCC27XXX10_config.taskStackSize;
           taskParams.arg       = (void *)config;

           /* Construct a task for handling CANCC27XX interrupts */
           if (TaskP_construct(&CANCC27XX_task, CANCC27XX_taskFxn, &taskParams) == NULL)
           {
               SemaphoreP_destruct(&CANCC27XX_irqSemaphore);
               SemaphoreP_destruct(&CANCC27XX_writeSemaphore);
               status = CAN_STATUS_ERROR;
           }
       }

       if (status == CAN_STATUS_SUCCESS)
       {
           /* Set a power dependency on DMA */
           Power_setDependency(PowerLPF3_PERIPH_DMA);
       }

       return status;
   }

Cleanup
~~~~~~~

When closing the driver, ensure you:

1. Destroy the IRQ handling task
2. Destroy semaphores
3. Release power dependencies

.. code:: c

   void CANCC27XXXX_close(CAN_Handle handle)
   {
       (void)handle; /* unused arg */

       /* Destroy the IRQ handling task */
       TaskP_destruct(&CANCC27XX_task);

       /* Destroy the IRQ handling semaphore */
       SemaphoreP_destruct(&CANCC27XX_irqSemaphore);

       /* Destroy the write semaphore */
       SemaphoreP_destruct(&CANCC27XX_writeSemaphore);

       /* Release the DMA power dependency */
       Power_releaseDependency(PowerLPF3_PERIPH_DMA);
   }


Putting It All Together – What a New Driver Must Implement
----------------------------------------------------------

+---------+------------------------------+-----------------------------+
| Requ    | Implementation in the        | What you must do in a       |
| irement | reference driver             | custom driver               |
+=========+==============================+=============================+
| **I     | ``CA                         | Replicate the array and use |
| dentify | NCC27XX_mcanCriticalRegs[]`` | it in a helper that decides |
| c       |                              | whether a write needs DMA.  |
| ritical |                              |                             |
| regi    |                              |                             |
| sters** |                              |                             |
+---------+------------------------------+-----------------------------+
| **DMA   | ``CANCC27XX_writeRegDma()``  | Use the exact code sequence |
| write   |                              | provided in CANCC27XXX10.c: |
| h       |                              | acquire CommonResource      |
| elper** |                              | lock, disable interrupts,   |
|         |                              | configure a one‑shot uDMA   |
|         |                              | channel, poll for           |
|         |                              | completion, restore state.  |
+---------+------------------------------+-----------------------------+
| **Defer | HWI (``CANCC27XX_hwiFxn``) + | Create a binary semaphore,  |
| ISR     | task (``CANCC27XX_taskFxn``) | post it from the ISR, and   |
| work**  |                              | run a task that calls the   |
|         |                              | full CAN ISR.               |
+---------+------------------------------+-----------------------------+
| **O     | ``CANClearInt``,             | Re‑implement every function |
| verride | ``CANSSSetClkStopCtrl``, etc | that writes a critical      |
| weak    |                              | register so it calls the    |
| dr      |                              | DMA helper.                 |
| iverlib |                              |                             |
| func    |                              |                             |
| tions** |                              |                             |
+---------+------------------------------+-----------------------------+
| **Se    | ``CAN_write``,               | Add a binary semaphore      |
| rialize | ``CAN_writeBuffer`` with     | around the whole Tx path    |
| Tx      | ``CANCC27XX_writeSemaphore`` | (including any              |
| buffer/ |                              | DMA‑protected register      |
| request |                              | writes).                    |
| a       |                              |                             |
| ccess** |                              |                             |
+---------+------------------------------+-----------------------------+
| **Ini   | ``CANCC27XXXX_init()``       | Follow the same init        |
| tialize | creates the semaphores, task | sequence: create binary     |
| reso    | and DMA dependency           | semaphores, construct the   |
| urces** |                              | task, enable DMA power, and |
|         |                              | call                        |
|         |                              | ``CommonResourceXXF         |
|         |                              | 3_constructRTOSObjects()``. |
+---------+------------------------------+-----------------------------+


Quick Checklist for Your Custom Driver
--------------------------------------

1. **Implement a special CAN register write function** which will
   redirect all critical register writes to the DMA-based write helper
   function.
2. **Use ``CANCC27XX_writeRegDma()``** without any modifications – keep
   the lock, PRIMASK handling, DMA config, and poll loop exactly as
   shown.
3. **Replace all direct register writes** (``HWREG(addr) = value;``)
   that target a critical register with a call to the DMA helper.
4. **Create a binary semaphore** (``CANCC27XX_writeSemaphore``) and use
   it in CAN write functions.
5. **Implement a deferred‑interrupt task**:

   -  HWI only disables the IRQ and posts a semaphore.
   -  Task waits on that semaphore, then runs the existing ISR body (you
      can copy ``CANCC27XX_irqHandler``).

6. **Override the weak driver library functions** (e.g.,
   ``CANClearInt``, ``CANSSSetClkStopCtrl``, ``CANSSClearClkStopCtrl``,
   ``CANSSSetEndOfInt``).
7. **Initialize everything** in the driver’s init routine (semaphores,
   task, DMA power dependency).
8. **Cleanup** everything that was initialized during init when closing
   the driver.



References
----------

`TI SYS-211 <https://www.ti.com/lit/er/swrz161a/swrz161a.pdf>`_