This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

AM3358: Cannot call SPI_transfer in Hwi context

Part Number: AM3358
Other Parts Discussed in Thread: UNIFLASH

Hi,

I'm trying to use the SPI driver from pdk_am335x_1_0_16 in my application.  In my use case I need to make a 12 byte SPI transaction as part of an interrupt.

Looking at the example code from the PDK, I set up my SPI params like this:

SPI_Params_init(&spiParams);
spiParams.frameFormat  = SPI_POL0_PHA0;
spiParams.bitRate = 1000000;
spiParams.transferMode = SPI_MODE_CALLBACK;
spiParams.transferCallbackFxn = MCSPICallbackFxn;

And then open the SPI instance like this:

spi_handle = SPI_open(1, &spiParams);       /* '1' comes from the MCSPI instance we want to open */

This all seems to work out just fine.  However, when I call SPI_transfer within a Hwi I get a crash and ROV tells me:

{module#28}: line 301: assertion failure: A_badContext: bad calling context. Must be called from a Task.
xdc.runtime.Error.raise: terminating execution

Looking into the MCSPI driver code, I see the crash happens here:

static bool MCSPI_transfer_v1(MCSPI_Handle mcHandle, SPI_Transaction *transaction)
{
    SPI_Handle            handle;
    SPI_v1_Object        *object;
    SPI_v1_chObject      *chObj;
    SPI_v1_HWAttrs const *hwAttrs;
    uint32_t              chNum, clrInt = 0U;;
    bool                  ret_val = (bool)false;
    SemaphoreP_Status   semStatus = SemaphoreP_OK;

    /* Input parameter validation */
    if ((mcHandle != NULL) && (transaction != NULL))
    {
    /* Get the pointer to the object and hwAttrs */
    handle  = mcHandle->handle;
    chNum   = mcHandle->chnNum;
    object = (SPI_v1_Object *)handle->object;
    chObj   = &(object->chObject[chNum]);
    hwAttrs = (const SPI_v1_HWAttrs *)handle->hwAttrs;

    /* Acquire the lock for the SPI transfer on this instance */
    (void)SPI_osalPendLock(object->mutex, SemaphoreP_WAIT_FOREVER);
    if (chObj->spiParams.transferTimeout == 0U)
    {
        /* timeout cannot be NO_WAIT, set it to default value */
        chObj->spiParams.transferTimeout = SPI_WAIT_FOREVER;
    }

    if ((uint32_t)transaction->count != 0U)
    {
        transaction->status = SPI_TRANSFER_STARTED;

SPI_osalPendLock is the culprit (assuming you cannot wait forever inside a Hwi which makes sense).

According to the comments in header file SPI.h, I can call SPI_transfer within the context of an Hwi:

 *  In ::SPI_MODE_CALLBACK, SPI_transfer() does not block task execution and
 *  calls a ::SPI_CallbackFxn. This makes the SPI_tranfer() safe to be used
 *  within a Task, Swi, or Hwi context. The ::SPI_Transaction structure must
 *  stay persistent until the SPI_transfer function has completed!

So, am I doing something wrong or are the comments incorrect in the header file and I cannot actually call SPI_transfer from an Hwi?  I have tried calling SPI_transfer outside of the Hwi in a task and it works OK.

Kind regards,

Simon

  • Hi Simon,

    I'm looking into this and should have a response to you by Monday.

    Thanks,

    Max

  • Hi Simon,

    I've asked the developer of the driver for their thoughts, and I will get back to you soon.

    Thanks,

    Max

  • Hi Max,

    Thanks for the updates.  I will look forward to seeing the response.

    Regards,

    Simon

  • Hi Simon,

    I'm currently trying to reproduce the issue, and should have a response to you by Friday.

    Thanks,

    Max

  • Hi Max,

    It's been a while... any update please?

    Thanks,

    Simon

  • Hi Max,

    Bumping this issue again as there hasn't been a response.  Please help!

    Thanks,

    Simon

  • Another bounce... please help as I haven't had a response yet.

  • Hi Simon,

    Apologies for the delayed response. This thread has now been assigned to me. I need to research this further, and can start looking into it more starting Tuesday.

    Regards,

    Dillon

  • Hi Simon, 

    Calling SPI_transfer() with SPI_MODE_CALLBACK in a Hwi is supported. 

    When do you post the mutex? And what does your MCSPICallbackFxn look like?

    Regards,

    Dillon

  • Hi Dillon,

    Maybe I don't understand the mechanism for using SPI with a callback.  I don't have anything in my MCSPICallbackFxn as I thought that this callback is called once the SPI transaction has completed.  I also thought that the mutex was handled in the SPI driver.

    Could you point me to some example code that I can use as a template?

    Thanks,

    Simon

  • Hi Simon,

    One example is located within the Uniflash source code. The SPI code is located in pdk_am335x_1_0_17/packages/ti/board/utils/uniflash/target/src/spi. This code also sets some additional hwAttrs configuration. Do you have any other configuration steps in your code?

    static int8_t UFP_spiInit(void)
    {
            SPI_Params spiParams;       /* SPI params structure */
    
            ((SPI_v1_HWAttrs *)SPI_config[MCSPI_INSTANCE].hwAttrs)->enableIntr = false;
            ((SPI_v1_HWAttrs *)SPI_config[MCSPI_INSTANCE].hwAttrs)->chNum = 0;
    
            SPI_init();

    Regards,

    Dillon

  • Hi Dillon,

    The Uniflash code does not appear to use TI RTOS so I cannot use that as an example.  I can use the SPI peripheral with ease but I seem unable to call SPI_transfer from within the context of an interrupt.

    I don't have any other configurations steps in my code beyond those posted at the beginning of the question. 

    So, given that the peripheral is configured correctly how else should I be using the PDK?

  • Hi Simon, 

    Here are some helpful examples that I found, with the functions that are most relevant to you. Each is slightly different, and so they should provide some good options to compare your code with.

    1. pdk/packages/ti/board/diag/mcspi/src/mcspi_diag.c     
      1. spi_test()
      2. spi_flash_test()
    2. pdk/packages/ti/board/diag/common/AM335x/diag_
      1. spi.c
      2. spiImageCopy()
    3. pdk/packages/ti/board/diag/qspi/src/qspi_diag.c
      1. spi_test()
    4. pdk/packages/ti/drv/spi/test/src/main_mcspi_test.c
      1. spi_test_multiple_channel()
      2. spi_test_transfer()
      3. MCSPI_callback0()
    5. pdk/packages/ti/drv/spi/example/mcspi_serializer/src/main_mcspi_example.c
      1. spi_test()
    6. pdk/packages/ti/drv/spi/example/mcspi_slavemode/src/main_mcspi_slave_mode.c
      1. SPI_test_multi_channel()

    One example in particular that I recommend checking out is:

    • pdk/packages/ti/drv/spi/example/firmware_example/src/main_test.c
      • espi_test()
      • HwiP_enableInterrupt()

    This example deals directly with HW interrupts.

    Also, do you call SPI_init() in your code?

    Regards,

    Dillon

  • Hi Dillon,

    I'm afraid that none of the examples show calling a transfer function within an HWI context.  Just to reiterate, I can use the SPI peripheral quite happily in polling and interrupt mode so I am calling SPI_Init() in my code and I have verified that I do get SPI transactions happening on my hardware.  What I am after is the ability to start an SPI transaction within the context of a different HWI context (in my case an interrupt that occurs once a McASP receive event happens).

    I have been through all of the examples within the PDK can cannot find an example where SPI_transfer is called within either a HW or SW interrupt context.

    So, given all of the above are there any other examples that meet my requirements or will I have to work on an alternative method?  My current temporary solution is to post a semaphore within the McASP HWI and have a task that pends on this before calling the SPI transfer function.

  • Hi Simon,

    From what I found, we do not have an example that meets those exact requirements, However, I will contact the development team about this and get back to you with what they say. Since there's a holiday this week, responses may be delayed, but I will make sure to get back to you by December 1st.

    In the mean time, working on your alternative method seems like the best option for now.

    Regards,

    Dillon

  • Hi Simon,

    Just as a test, try adjusting your code to use OSPI_open_v0(), and set hwAttrs of intrEnable to true, and dacEnable to false.

    This is because OSPI_open_v0() shows support for interrupt mode, as long as those options are configured properly:

            /* interrupt mode only enabled in indirect access controller mode */
            if (((bool)true == hwAttrs->intrEnable) && ((bool)false == hwAttrs->dacEnable))
            {
                Osal_RegisterInterrupt_initParams(&interruptRegParams);
    
                interruptRegParams.corepacConfig.name=NULL;
    #ifndef __TI_ARM_V7R5__
                interruptRegParams.corepacConfig.priority=0x8U;
    #else
                interruptRegParams.corepacConfig.priority=0x20U;
    #endif
                interruptRegParams.corepacConfig.corepacEventNum = (int32_t)hwAttrs->eventId;
                interruptRegParams.corepacConfig.intVecNum = (int32_t)hwAttrs->intrNum; /* Host Interrupt vector */
                interruptRegParams.corepacConfig.isrRoutine = (void (*)(uintptr_t))(&OSPI_hwiFxn_v0);
                interruptRegParams.corepacConfig.arg = (uintptr_t)handle;
    
                (void)SPI_osalRegisterInterrupt(&interruptRegParams,&(object->hwi));
                if(object->hwi == NULL) {
                    OSPI_close_v0(handle);
                    retHandle = NULL;
                    retFlag = 1;
                }
            }
    

    This will also configure the ISR, which is currently set to a generic interrupt handler (OSPI_hwiFxn_v0), but you can change it to your own interrupt handler and transfer your data from there.

    Let me know if this test works for you.

    Regards,

    Dillon

  • Hi Dillon,

    Thanks for this information.  I will try and get back to this project as soon as I can (perhaps within a week).

    Kind regards,

    Simon