RPMsg char driver not sending data on iMX8MM

Hello,

I’m pretty new to embedded Linux development and embedded development in general. I’m trying to implement inter-processor communication on the Verdin iMX8MM DL 1GB V1.1A module. I’m using the Verdin Development Board V1.1B as a carrier board. I’m not using Torizon Core, but a slightly altered version of the Minimal Reference Image (as to make RPMsg work correctly and build a sample hello-world program and put it into the /sbin/ directory).

I can’t get the rpmsg_char kernel driver to send messages to the Cortex-M4 core. I’ve managed to get the TTY driver working, but that one creates too much delay for my use case. The Cortex-M4 is running the rpmsg_lite_str_echo_rtos example from the SDK. For the Linux user-space program, I’m using the example code provided here, with the write and read system calls swapped.

Upon executing the hello-world program from the Linux user-space, the /dev/rpmsg0 character device is successfully created, but writing to it gives no results on the M4. This, in turn, makes the hello-world program hang on the call to read since the M4 did not send its echo reply.

I’ve tried changing both src and dst to various values, as well as changing the channel name as to match the one announced by the M4, but to no avail. Any suggestions on how I can make this work?

Hi @NickVO,

I will test the same scenario on my side and reply to you as soon as I have something.

Best Regards,
Hiago.

Hey @hfranco.tx,

Thanks for looking into it! Have you managed to recreate the scenario yet? If there is any other information or code fragments you need, feel free to ask.

Hi @NickVO,

From what I’ve been looking at here, the ping pong example from NXP uses the char device instead of the tty. So I created this overlay and the example works:

// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
/*
 * Copyright 2022 Toradex
 */

// Enable RPMSG and the M4 driver

/dts-v1/;
/plugin/;

#include <dt-bindings/clock/imx8mm-clock.h>

/ {
	compatible = "toradex,verdin-imx8mm";
};

&{/} {
	imx8mm-cm4 {
		compatible = "fsl,imx8mm-cm4";
		rsc-da = <0xb8000000>;
		clocks = <&clk IMX8MM_CLK_M4_DIV>;
		mbox-names = "tx", "rx", "rxdb";
		mboxes = <&mu 0 1
			  &mu 1 1
			  &mu 3 1>;
		memory-region = <&vdevbuffer>, <&vdev0vring0>, <&vdev0vring1>, <&rsc_table>, <&m4_reserved>;
		syscon = <&src>;
		/*
		 * The "fsl,startup-delay-ms" is required in case of
		 * "fsl,auto-boot" is used. It will make sure the
		 * /lib/firmware is mounted before the imx_rproc driver
		 * tries to launch the M4 firmware.
		 */
		fsl,startup-delay-ms = <500>;
	};
};

&mu {
	status = "okay";
};

&uart4 {
	status = "disabled";
};

&resmem {
	#address-cells = <2>;
	#size-cells = <2>;

	m4_reserved: m4@0x80000000 {
		no-map;
		reg = <0 0x80000000 0 0x1000000>;
	};

	vdev0vring0: vdev0vring0@b8000000 {
		reg = <0 0xb8000000 0 0x8000>;
		no-map;
	};

	vdev0vring1: vdev0vring1@b8008000 {
		reg = <0 0xb8008000 0 0x8000>;
		no-map;
	};

	rsc_table: rsc_table@b80ff000 {
		reg = <0 0xb80ff000 0 0x1000>;
		no-map;
	};

	vdevbuffer: vdevbuffer@b8400000 {
		compatible = "shared-dma-pool";
		reg = <0 0xb8400000 0 0x100000>;
		no-map;
	};

};

(By the way, you can download the compiled binary here: Download - Toradex File Sharing Platform)

Next, I compiled the rpmsg_lite_pingpong_rtos example and it worked on my Verdin iMX8M Mini with this overlay enabled.

Checking the source code of the driver and also the source code of the M4 code, it appears that the tty is not being used (on my linux, the device /dev/rpmsg_ctrl0 was used).

I will also try to create a small example on my side based on the ping pong example where I can exchange some data between the two cores, but can you check this source code to check how the char device is being used?

Let me know if you have any questions.

Best Regards,
Hiago.

Hi @hfranco.tx,

Are you testing with the imx_rpmsg_pingpong kernel module or the rpmsg_char one? I’m trying to get the latter to work. My kernel version is 5.4.193, just in case.

I’ve tried loading your DTO, but this seems to not start up the RPMsg driver correctly. At the moment I’m using the one you posted here.

Here is the source code I’m testing with for both the A53 and M4:

M4:

/*
 * Copyright (c) 2016, Freescale Semiconductor, Inc.
 * Copyright 2016-2020 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "rpmsg_lite.h"
#include "rpmsg_queue.h"
#include "rpmsg_ns.h"

#include "pin_mux.h"
#include "clock_config.h"
#include "board.h"
#include "fsl_debug_console.h"

#include "FreeRTOS.h"
#include "task.h"

#include "fsl_uart.h"
#include "rsc_table.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/
#define RPMSG_LITE_LINK_ID            (RL_PLATFORM_IMX8MM_M4_USER_LINK_ID)
#define RPMSG_LITE_SHMEM_BASE         (VDEV0_VRING_BASE)
#define RPMSG_LITE_NS_ANNOUNCE_STRING "rpmsg-openamp-demo-channel"
#define RPMSG_LITE_MASTER_IS_LINUX

#define APP_DEBUG_UART_BAUDRATE (115200U) /* Debug console baud rate. */
#define APP_TASK_STACK_SIZE (256U)
#ifndef LOCAL_EPT_ADDR
#define LOCAL_EPT_ADDR (30U)
#endif
#define APP_RPMSG_READY_EVENT_DATA (1U)

#define RPMSG_DATA_SIZE 256

typedef struct {
    char data[RPMSG_DATA_SIZE];
} msg_data_t;

static volatile msg_data_t msg = {};
#ifdef RPMSG_LITE_MASTER_IS_LINUX
static char helloMsg[13];
#endif /* RPMSG_LITE_MASTER_IS_LINUX */

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

/*******************************************************************************
 * Code
 ******************************************************************************/
static TaskHandle_t app_task_handle = NULL;

static struct rpmsg_lite_instance *volatile my_rpmsg = NULL;

static struct rpmsg_lite_endpoint *volatile my_ept = NULL;
static volatile rpmsg_queue_handle my_queue        = NULL;

void app_destroy_task(void)
{
    if (app_task_handle)
    {
        vTaskDelete(app_task_handle);
        app_task_handle = NULL;
    }

    if (my_ept)
    {
        rpmsg_lite_destroy_ept(my_rpmsg, my_ept);
        my_ept = NULL;
    }

    if (my_queue)
    {
        rpmsg_queue_destroy(my_rpmsg, my_queue);
        my_queue = NULL;
    }

    if (my_rpmsg)
    {
        rpmsg_lite_deinit(my_rpmsg);
        my_rpmsg = NULL;
    }
}

static void app_nameservice_isr_cb(uint32_t new_ept, const char *new_ept_name, uint32_t flags, void *user_data)
{
}

#ifdef MCMGR_USED
/*!
 * @brief Application-specific implementation of the SystemInitHook() weak function.
 */
void SystemInitHook(void)
{
    /* Initialize MCMGR - low level multicore management library. Call this
       function as close to the reset entry as possible to allow CoreUp event
       triggering. The SystemInitHook() weak function overloading is used in this
       application. */
    (void)MCMGR_EarlyInit();
}
#endif /* MCMGR_USED */

static void app_task(void *param)
{
    volatile uint32_t remote_addr;
    volatile rpmsg_ns_handle ns_handle;

    /* Print the initial banner */
    (void)PRINTF("\r\nRPMSG Echo FreeRTOS RTOS API Demo...\r\n");

#ifdef MCMGR_USED
    uint32_t startupData;
    mcmgr_status_t status;

    /* Get the startup data */
    do
    {
        status = MCMGR_GetStartupData(&startupData);
    } while (status != kStatus_MCMGR_Success);

    my_rpmsg = rpmsg_lite_remote_init((void *)(char *)startupData, RPMSG_LITE_LINK_ID, RL_NO_FLAGS);

    /* Signal the other core we are ready by triggering the event and passing the APP_RPMSG_READY_EVENT_DATA */
    (void)MCMGR_TriggerEvent(kMCMGR_RemoteApplicationEvent, APP_RPMSG_READY_EVENT_DATA);
#else
    (void)PRINTF("RPMSG Share Base Addr is 0x%x\r\n", RPMSG_LITE_SHMEM_BASE);
    my_rpmsg = rpmsg_lite_remote_init((void *)RPMSG_LITE_SHMEM_BASE, RPMSG_LITE_LINK_ID, RL_NO_FLAGS);
#endif /* MCMGR_USED */
    rpmsg_lite_wait_for_link_up(my_rpmsg);
    (void)PRINTF("Link is up!\r\n");

    my_queue  = rpmsg_queue_create(my_rpmsg);
    my_ept    = rpmsg_lite_create_ept(my_rpmsg, LOCAL_EPT_ADDR, rpmsg_queue_rx_cb, my_queue);
    ns_handle = rpmsg_ns_bind(my_rpmsg, app_nameservice_isr_cb, ((void *)0));
    /* Introduce some delay to avoid NS announce message not being captured by the master side.
       This could happen when the remote side execution is too fast and the NS announce message is triggered
       before the nameservice_isr_cb is registered on the master side. */
    SDK_DelayAtLeastUs(1000000U, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);
    (void)rpmsg_ns_announce(my_rpmsg, my_ept, RPMSG_LITE_NS_ANNOUNCE_STRING, (uint32_t)RL_NS_CREATE);
    (void)PRINTF("Nameservice announce sent.\r\n");

#ifdef RPMSG_LITE_MASTER_IS_LINUX
    /* Wait Hello handshake message from Remote Core. */
    (void)rpmsg_queue_recv(my_rpmsg, my_queue, (uint32_t *)&remote_addr, helloMsg, sizeof(helloMsg), ((void *)0),
                           RL_BLOCK);
#endif /* RPMSG_LITE_MASTER_IS_LINUX */

    while (true) {
        (void)PRINTF("Waiting for data...\r\n");
        (void)rpmsg_queue_recv(
            my_rpmsg, my_queue,
            (uint32_t *)&remote_addr,
            (char *)&msg,
            sizeof(msg_data_t),
            ((void *)0), RL_BLOCK
        );

        (void)PRINTF("A53: %s\r\n", msg.data);

        (void)PRINTF("Sending echo...\r\n");
        (void)rpmsg_lite_send(
            my_rpmsg,
            my_ept,
            remote_addr,
            (char *)&msg,
            sizeof(msg_data_t),
            RL_BLOCK
        );
    }

    (void)rpmsg_lite_destroy_ept(my_rpmsg, my_ept);
    my_ept = ((void *)0);

    (void)rpmsg_queue_destroy(my_rpmsg, my_queue);
    my_queue = ((void *)0);

    (void)rpmsg_ns_unbind(my_rpmsg, ns_handle);

    (void)rpmsg_lite_deinit(my_rpmsg);
    my_rpmsg = ((void *)0);

    (void)PRINTF("Looping forever...\r\n");

    /* End of the example */
    for (;;)
        ;
}

void app_create_task(void)
{
    if (xTaskCreate(app_task, "APP_TASK", APP_TASK_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, &app_task_handle) != pdPASS)
    {
        PRINTF("\r\nFailed to create application task\r\n");
        for (;;)
            ;
    }
}

/*!
 * @brief Main function
 */
int main(void)
{
    /* Initialize standard SDK demo application pins */
    /* Board specific RDC settings */
    BOARD_RdcInit();

    BOARD_InitBootPins();
    BOARD_BootClockRUN();
    BOARD_InitDebugConsole();
    BOARD_InitMemory();

    copyResourceTable();

#ifdef MCMGR_USED
    /* Initialize MCMGR before calling its API */
    (void)MCMGR_Init();
#endif /* MCMGR_USED */

    app_create_task();
    vTaskStartScheduler();

    (void)PRINTF("Failed to start FreeRTOS on core0.\r\n");
    for (;;)
        ;
}

A53:

#include <linux/rpmsg.h>

#include <sys/ioctl.h>

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#define RPMSG_DATA_SIZE 256

typedef struct {
    char data[RPMSG_DATA_SIZE];
} msg_data_t;

int main(void)
{
    msg_data_t data_buf = {
        "Hello World!"
    };

    fprintf(stderr, "==========================\n");
    fprintf(stderr, "* RPMsg Hello World demo *\n");
    fprintf(stderr, "==========================\n");

    fprintf(stderr, "Creating endpoint...\n");
    struct rpmsg_endpoint_info ept_info = {"rpmsg-openamp-demo-channel", 0x2, 0x1e};
    int fd = open("/dev/rpmsg_ctrl0", O_RDWR);
    fprintf(stderr, "File descriptor created '%d'\n", fd);
    fprintf(stderr, "Channel info: '%s' [ '%x' --> '%x' ]\n", ept_info.name, ept_info.src, ept_info.dst);

    /* create endpoint interface */
    ioctl(fd, RPMSG_CREATE_EPT_IOCTL, &ept_info);  // /dev/rpmsg0 is created
    fprintf(stderr, "Endpoint created\n");

    /* create endpoint */
    int fd_ept = open("/dev/rpmsg0", O_RDWR); // backend creates endpoint
    fprintf(stderr, "Endpoint file descriptor created '%d'\n", fd_ept);

    /* send data to remote device */
    fprintf(stderr, "Sending...\n");
    write(fd_ept, &data_buf, sizeof(data_buf));

    /* receive data from remote device */
    // fprintf(stderr, "Receiving...\n");
    // read(fd_ept, &data_buf, sizeof(data_buf));

    /* destroy endpoint */
    fprintf(stderr, "Destroying endpoint...\n");
    ioctl(fd_ept, RPMSG_DESTROY_EPT_IOCTL);
    fprintf(stderr, "Endpoint destroyed\n");

    close(fd_ept);
    close(fd);

    return 0;
}

Maybe you see what I’m doing wrong here. I suspect I’m creating my endpoint incorrectly through the /dev/rpmsg_ctrl0 control device in the A53 core, but I could be completely wrong :sweat_smile:

Best regards,
Nick

Hi @NickVO,

I’m sorry, I was testing everything on BSP 6, kernel 5.15. This overlay won’t work for 5.4. I will switch to BSP 5.7 then. This overlay from the other thread that you shared should work for 5.7 in this case.
I’m testing the pingpong.

Thanks! I will test them on my side and check if I find something.

Best Regards,
Hiago.

Hey @hfranco.tx,

I’ve managed to get the rpmsg_char driver working on BSP 6, kernel 5.15. No changes were necessary to my previously provided source code, only updating to the newer BSP, using your provided overlay, and figuring out how remoteproc worked. :smile: Though, it’s still strange that the driver wouldn’t work on the older BSP…

Anyways, thanks for nudging me in the right direction!

Best regards,
Nick

Hi @NickVO,

I’m glad it works now! I will mark the previous answer as solved then. Please let me know if you have any further questions.

Best Regards,
Hiago.