Skip to content

It should be possible to write out a serial port asynchronously without DMA (using DMA_USAGE_NEVER) #3535

@Patater

Description

@Patater

Description

  • Type: Bug
  • Priority: Major

Bug

Target
Observed on K64F
Should be reproducible on any other target supporting async serial and DMA

Toolchain:
GCC_ARM

Toolchain version:
arm-none-eabi-gcc (GNU Tools for ARM Embedded Processors) 5.4.1 20160919 (release) [ARM/embedded-5-branch revision 240496]

mbed-cli version:
0.9.10

meed-os sha:
e7361eb with additional patch applied from NXP, "K64F: Add support for SERIAL ASYNCH API"
Use PR #3438
or use my branch at https://github.com/Patater/mbed-os/tree/k64f-serial-dma-fun)

I'd expect the bug would be reproducible any target that supports the serial async API with DMA, however.

Expected behavior
It should be possible to write out a serial port asynchronously without DMA.

Actual behavior
The write goes out the serial port, but we never receive a TX completion event (SERIAL_EVENT_TX_COMPLETE or anything else matching SERIAL_EVENT_TX_ALL)

If no DMA channels are available or if the DMA usage hint DMA_USAGE_NEVER is provided, the serial driver is supposed to fall back to using interrupts. However, it looks like that fallback behavior isn't working. After a quick inspection of the code, I couldn't see where the UART's interrupt is enabled: EnableIRQ is called, but K64F's UART_EnableInterrupts or the HAL's serial_irq_set appears not to be called, so the UART appears to never trigger the IRQ and we never get a TX completion event at the application level.

Steps to reproduce
I was able to reproduce the issue on K64F with the following steps. However, one should be able to reproduce the issue on any other target with DEVICE_SERIAL_ASYNCH

  1. Make a new project (mbed new serial_dma_fun)
  2. Get mbed-os from PR K64F: Add support for SERIAL ASYNCH API #3438 (or use my branch at https://github.com/Patater/mbed-os/tree/k64f-serial-dma-fun)
  3. Create main.cpp as below
  4. Compile and run (mbed compile -m K64F -t GCC_ARM)
/* main.cpp */
#include <stdint.h>

#include <mbed.h>
#include <rtos.h>

#if !DEVICE_SERIAL_ASYNCH
#error "This test suite requires the async serial API to be supported on this target"
#endif

/* This semaphore keeps track of whether or not the serial callback has
 * finished. This semaphore allows the main thread to wait for the serial
 * callback to finish. */
Semaphore serial_semaphore(0);

Timeout watchdog;

static void serial_write_callback(int events)
{
    serial_semaphore.release();
}

static void fail() {
    puts("\r\n\tSorry, we failed something.\r\n");
    for(;;);
}

static void dma_serial_write_async(const DMAUsage usage)
{
    int result;
    Serial serial(USBTX, USBRX);
    event_callback_t event;
    const uint8_t buffer[] = "Howdy\r\n";

    event.attach(serial_write_callback);

    /* Configure DMA */
    result = serial.set_dma_usage_tx(usage);
    if (result) {
        fail();
    }
    result = serial.set_dma_usage_rx(usage);
    if (result) {
        fail();
    }

    serial.write(buffer, sizeof(buffer) - 1, event, SERIAL_EVENT_TX_ALL);

    /* Wait for the write to finish by waiting for the serial callback to
     * finish. */
    /* NOTE: Completion event (callback) never happens, even though we expect
     * such an event, but the serial data does still go out the serial port. */
    serial_semaphore.wait();
}

static void dma_serial_write_async_without_dma(void)
{
    printf("-------- %s --------", __func__);
    fflush(stdout);
    dma_serial_write_async(DMA_USAGE_NEVER);
    puts("\r\n");
    fflush(stdout);
}

static void timed_out() {
    /* We've timed out. */
    puts("\r\n\tSorry, looks like we hung.\r\n");
}

static void setup_watchdog(void)
{
    /* Set up a watchdog timer to kill things that hang. This will call our
     * callback in ISR mode after 2 seconds. */
    watchdog.attach(timed_out, 2.0);
}

int main(void)
{
    puts("\r\nHello and welcome to serial DMA fun!\r\n");

    setup_watchdog();

    /* This will hang. */
    dma_serial_write_async_without_dma();

    puts("Congratulations! You've made it through the gauntlet!\r\n");
}

The output of running the program is as follows. The "Howdy" text is what we attempted to write out the serial port with asynchronously with the DMA hint DMA_USAGE_NEVER.

Hello and welcome to serial DMA fun!

-------- dma_serial_write_async_without_dma -------Howdy

	Sorry, looks like we hung.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions