Mailbox

[English]

1 功能概述

Mailbox主要用于核间通信,BK7258实现了多种接口,用户可根据具体业务需求选择使用。主要有以下三种:
1)mailbox通道API;
        2)MB_IPC API(采用了类似socket的API接口和运行方式)
        3)MB_UART API(模拟带自动流控的UART)

2 接口类型1:mailbox通道使用说明

  • mailbox通道API介绍:

    在mailbox_channel.c和mailbox_channel.h 中实现了用于核间通信的最多16个mailbox通道,在需要用新的通道实现核间通信时,先在mailbox_channel.h中定义一条通道的名字(枚举名),需要在两核中同时添加,而且必须是相同的枚举值。 实现核间通信的代码非常简单,主要关注mb_chnl_write,以及两个通过mb_chnl_ctrl注册的回调函数。 还有一个特别注意的地方是:用mailbox通道做核间通信时,传递的是本核(源核)的内存指针给另外一个核(目的核),在目的核开cache的情况下,都要对收到的指针进行flush_dcache,确保读到的是最新的数据,参见第2节例子1中的代码。

1)mb_chnl_open
    /*
      * open logical chnanel.
      * input:
      *     log_chnl  : logical channel id to open.
      *     callback_param : param passsed to all callbacks.
      * return:
      *     succeed: BK_OK;
      *     failed  : fail code.
      *
      */
    bk_err_t mb_chnl_open(u8 log_chnl, void * callback_param);
    callback_param:在调用用户注册的回调函数时,都会回传这个参数。
                这个参数用户可以根据需要设置,如果上层不需要这个参数,可以传NULL。


2)mb_chnl_close
    /*
      * close logical chnanel.
      * input:
      *     log_chnl  : logical channel id to close.
      * return:
      *     succeed: BK_OK;
      *     failed  : fail code.
      *
      */
    bk_err_t mb_chnl_close(u8 log_chnl);

3) mb_chnl_write
    /*
      * write to logical chnanel.
      * input:
      *     log_chnl     : logical channel id to write.
      *     cmd_buf       : command buffer to send.
      *
      * return:
      *     succeed: BK_OK;
      *     failed  : fail code.
      *
      */
    bk_err_t mb_chnl_write(u8 log_chnl, mb_chnl_cmd_t * cmd_buf);
    把指定格式的缓冲区数据写到mailbox。
    typedef struct
    {
        mb_chnl_hdr_t   hdr;

        u32             param1;
        u32             param2;
        u32             param3;
    } mb_chnl_cmd_t;

    typedef union
    {
        struct
        {
                u32             cmd           :  8;
                u32             state         :  4;
                u32             Reserved      :  20;    /* reserved for system. */
        } ;
        u32             data;
    } mb_chnl_hdr_t;
    hdr字段中,用户只要设置cmd域,其他都保留,不需要区设置。

4)mb_chnl_ctrl
    /*
      * logical chnanel misc io (set/get param).
      * input:
      *     log_chnl     : logical channel id to set/get param.
      *     cmd          : control command for logical channel.
      *     param      :  parameter of the command.
      *        MB_CHNL_GET_STATUS:   param, (u8 *) point to buffer (one byte in size.) to receive status data.
      *        MB_CHNL_SET_RX_ISR:   param, pointer to rx_isr_callback.
      *        MB_CHNL_SET_TX_CMPL_ISR:   param, pointer to tx_cmpl_isr_callback.
      *        MB_CHNL_WRITE_SYNC:   param, pointer to mb_chnl_cmd_t buffer, write to mailbox synchronously.
      * return:
      *     succeed: BK_OK;
      *     failed  : fail code.
      *
      */
    bk_err_t mb_chnl_ctrl(u8 log_chnl, u8 cmd, void * param);

    cmd 是MB_CHNL_GET_STATUS时:param 指向一个字节大小的缓冲区,用于接收一个字节的返回值,
                指示通道是否空闲,0 表示通道空闲,其他值表示通道忙。
                通道空闲时,可以调用 mb_chnl_write 往逻辑通道写数据。
                通道忙时,调用mb_chnl_write写数据会失败,返回BK_ERR_BUSY。
    cmd 是MB_CHNL_SET_RX_ISR,MB_CHNL_SET_TX_CMPL_ISR时:
    param指向函数,是一个对应的函数指针。这些回调函数的原型如下:

          void  (* chnl_rx_isr)(void *param, mb_chnl_cmd_t *cmd_buf);

                      void  (* chnl_tx_cmpl_isr)(void *param, mb_chnl_ack_t *ack_buf);

    param,就是在通道open时传入的参数callback_param。
    通道收到命令时,调用chnl_rx_isr 回调函数,cmd_buf是收到的命令包,
                格式同对方调用mb_chnl_write时的参数mb_chnl_cmd_t * cmd_buf。
    chnl_rx_isr或者上层应用收到cmd_buf并处理完成后,需要给对方一个确认,
                确认包的格式是mb_chnl_ack_t。

    typedef struct
    {
        mb_chnl_hdr_t   hdr;

        u32             ack_data1;
        u32             ack_data2;
        union
        {
                u32             ack_data3;
                u32             ack_state; /* ack_state or ack_data3, depends on applications. */
        };
    } mb_chnl_ack_t;

    确认包ack_buf中hdr字段中的cmd 就是发送方的cmd,(也就是接收到的cmd_buf包中hdr字段中的cmd)。
    ACK包中的ack_data1、ack_data2、ack_data3/ack_state可以在确认的同时用来传输其他信息,
                其内容来自cmd_buf中的param1、param2、param3。
                也就是接收方可以把cmd_buf当成mb_chnl_ack_t类型的内存缓冲区,并修改ack_data1、ack_data2、ack_data3/ack_state的内容。
                在chnl_rx_isr 函数返回时候,自动把cmd_buf中的内容作为ack_buf送到发送方的 chnl_tx_cmpl_isr中(第二个参数)。
                如果接收方chnl_rx_isr没有任何改动cmd_buf,那么发送方的chnl_tx_cmpl_isr的ack_buf中的ack_data1、ack_data2、ack_data3/ack_state,
                就是发送时候的cmd_buf中的param1、param2、param3。

    如果双方约定通过ack_state来指示本次传输的处理状态,那么系统定义了如下3种状态:
    enum
    {
        ACK_STATE_PENDING = 0x01,
        ACK_STATE_COMPLETE,
        ACK_STATE_FAIL,
    };
    ACK_STATE_PENDING:表示正在处理该命令,如果双方有共用的缓冲区,该缓冲区正在使用中,不能释放。
    ACK_STATE_COMPLETE:cmd handling is competed, addtional infos in ack_data1 & ack_data2. 如果双方有共用的缓冲区,此时可以释放。
    ACK_STATE_FAIL: cmd failed, addtional infos in ack_data1 & ack_data2. 如果双方有共用的缓冲区,此时可以释放。
    如果双方不用ack_state来指示处理状态,处理方式双方上层应用已经约定好,可以不用ack_state,此时ack_state就是ack_data3。
    ack_data1,ack_data2,ack_data3可以在确认的同时来传输其他信息。

    通道发送mb_chnl_write中的命令,并获得对方确认时会调用应用注册的chnl_tx_cmpl_isr回调函数,
                该回调函数返回对方的确认数据,格式是mb_chnl_ack_t,如上所述。
    收到确认后如果双方约定通过ack_state来指示本次传输的处理状态,需要检查ack_state的值。
                此外,发送方还要检查ack 包中的hdr字段,如下,
    #define CHNL_STATE_MASK             0xF
    #define CHNL_STATE_COM_FAIL 0x1     /* NO target, it is an ACK to peer CPU. */
    typedef union
    {
        struct
        {
                u32             cmd          :  8;
                u32             state         :  4;
                u32             Reserved      :  20;    /* reserved for system. */
        } ;
        u32             data;
    } mb_chnl_hdr_t;

    hdr字段中,cmd 就是发送的cmd,state字段占4 bit,最低的bit0为通讯状态指示。如果该逻辑通道的对方,没有应用在使用该逻辑通道,
                那么本次发送的cmd包,就没有应用接收和处理,通讯的底层就在hdr.state指示这个状态,表示通信失败,
                如果此时有缓冲区发送给对方,收到通讯失败的确认后,可以在此刻释放缓冲区。
    hdr.state是逻辑通道的通讯层的状态指示。
    ack_state是逻辑通道的应用层的状态指示。

    cmd 是MB_CHNL_WRITE_SYNC时:param 指向mb_chnl_cmd_t 缓冲区, 该数据包直接写到物理通道中,
                如果物理通道不空闲,函数中等待直到通道空闲。这个命令用于系统崩溃时,把一些关键信息直接写出去。
  • mailbox通道代码示例:

#include <os/os.h>
#include <driver/mailbox_channel.h>

#define MOD_TAG   "MB_SAMPLE"

static uint8_t x_chnl_inited = 0;

static volatile uint8_t    tx_in_process = 0;
static volatile uint8_t    tx_failed = 0;

static beken_mutex_t       x_chnl_mutex = NULL;
static beken_semaphore_t   x_chnl_tx_sem = NULL;


static void x_mailbox_rx_isr(void *param,, mb_chnl_cmd_t *cmd_buf)
{
        uint8_t   chnl_id = (uint8_t)param;
        uint8_t * data_buf = (uint8_t *)cmd_buf->param1;
        uint32_t  data_len = cmd_buf->param2;

        if((data_buf != NULL) && (data_len > 0))
        {
                #if CONFIG_CACHE_ENABLE
                flush_dcache(data_buf, data_len);
                #endif

                // memcpy(rx_buf, data_buf, data_len);
                // or
                // rx_callback(chnl_id, data_buf, data_len)
        }

        return;

}

static void x_mailbox_tx_cmpl_isr(void *param, mb_chnl_ack_t *ack_buf)
{
        uint8_t   chnl_id = (uint8_t)param;

        if(tx_in_process != 0)
        {
                /* tx complete. */
                tx_in_process  = 0;

                if (ack_buf->hdr.state & CHNL_STATE_COM_FAIL)
                {
                        tx_failed = 1;
                }
                else
                {
                        /* communication ok, function is completed. */
                        tx_failed = 0;
                }

                rtos_set_semaphore(&x_chnl_tx_sem);

                return;
        }

        /*
         *   !!!  FAULT  !!!
         */
        BK_LOGE(MOD_TAG, "Fault in %s,chnl:0x%x, cmd:%d, 0x%x\r\n", __func__, chnl_id, ack_buf->hdr.cmd, ack_buf->ack_data1);

        return;

}

bk_err_t x_mailbox_init(uint8_t chnl_id)
{
        bk_err_t                ret_code;

        if(x_chnl_inited)
                return BK_OK;

        ret_code = rtos_init_mutex(&x_chnl_mutex);
        if(ret_code != BK_OK)
        {
                return ret_code;
        }

        ret_code = rtos_init_semaphore(&x_chnl_tx_sem, 1);
        if(ret_code != BK_OK)
        {
                rtos_deinit_mutex(&x_chnl_mutex);

                return ret_code;
        }

        ret_code = mb_chnl_open(chnl_id, (void *)chnl_id);
        if(ret_code != BK_OK)
        {
                rtos_deinit_mutex(&x_chnl_mutex);
                rtos_deinit_semaphore(&x_chnl_tx_sem);

                return ret_code;
        }

        mb_chnl_ctrl(chnl_id, MB_CHNL_SET_RX_ISR, (void *)x_mailbox_rx_isr);
        mb_chnl_ctrl(chnl_id, MB_CHNL_SET_TX_CMPL_ISR, (void *)x_mailbox_tx_cmpl_isr);

        x_chnl_inited = 1;

        return BK_OK;
}

extern int mb_ipc_cpu_is_power_on(u32 cpu_id);

bk_err_t mailbox_transfer(uint8_t chnl_id, uint8_t cmd, void *data_buf, uint32_t data_size)
{
        bk_err_t                ret_code;

        if(x_chnl_inited == 0)
                return BK_FAIL;

        uint8_t         dst_cpu = GET_DST_CPU_ID(chnl_id);

        if( !mb_ipc_cpu_is_power_on(dst_cpu) )
        {
                return BK_FAIL;  // or return other error, to indicate the target CPU is power-off
        }

        rtos_lock_mutex(&x_chnl_mutex);

        tx_in_process = 1;
        tx_failed = 1;

        mb_chnl_cmd_t mb_cmd;

        mb_cmd.hdr.cmd = cmd;
        mb_cmd.param1 = (uint32_t)data_buf;
        mb_cmd.param2 = data_size;
        mb_cmd.param3 = 0;

        ret_code = mb_chnl_write(chnl_id, &mb_cmd);

        if (ret_code != BK_OK)
        {
                BK_LOGE("%s comm failed\n", __func__);
                goto transfer_exit;
        }

        ret_code = rtos_get_semaphore(&x_chnl_tx_sem, BEKEN_WAIT_FOREVER); // 2000);

        if (ret_code != BK_OK)
        {
                tx_in_process = 0;

                BK_LOGE("%s wait semaphore failed\n", __func__);
                goto transfer_exit;
        }

        if( tx_failed != 0)
                ret_code = BK_FAIL; // CHNL_STATE_COM_FAIL, target is not initialized.

transfer_exit:

        rtos_unlock_mutex(&x_chnl_mutex);

        return ret_code;
}

3 接口类型2:MB_IPC (类似socket的API接口)

  • MB_IPC API介绍:

     MB_IPC整体上来说,采用了类似socket的API接口和运行方式,因为设计是基于小系统的核间通信,
         所以设计上相对于socket API的行为做了一些限制和简化修改。(IP:Port)替换为(CPU-ID:Port),用于标识通信的双方。
    
     首先,从最宏观方面,socket通信有连接方式和非连接方式,目前本设计的接口支持了半连接方式。
         也就是这个连接方式有个省略:没有做到3次握手,只是两次握手。
         连接建立后,数据传输时没有超时重发。发送方每个tx-cmd发送出去,接收方收到后必然会回复一个对应rx-rsp(ACK)。
         发送方在指定时间内未收到ACK,不做超时重传。收发接口都有一个timeout参数,用于阻塞等待多久,send接口用于等
         待rsp,recv接口用于等待对端send命令的到达。超时重传留给业务层完成。(在最终的代码实现中已经包含重传机制,
         目前没有打开,因为在SoC内通信中,通讯还是比较可靠的)跨核转发通信导致的mailbox中断次数,比直接用mailbox
         通道通信,中断次数增加一倍,(两核直连的mailbox通信,逻辑通道层顺带接收确认机制,为了屏蔽跨核转发通信细
         节,需要在SOCKET完成确认,增加一次ACK通信)。本设计实现不做超时重传,主要是考虑到SoC内的核间通信失败概率
         应该不多,后续若有必要,可以加入重传,在socket创建时统一设置超时时间和重传次数(而不是在每个接口加这两个
         参数)。
     另外,本设计对连接也不进行检测,也就是发现断连需要靠应用层自己。
     其次,基于连接的socket有listen socket 和connection socket概念,server端listen到有接入,内部新建一个
         独立的connection socket用于通信,通常实现上,也是会新建一个线程和客户端通信,实现高并发的通信。本设计没
         有让server端socket有两个独立的属性,而是一体的。也就是说,对server 端的socket,既是listen socket,又是
         connection socket,对每个连接上的客户端,server 逐个处理收发业务。主要是基于实现简单,而且server 需要连
         接多个client的场景也不普遍。另外就是不生成专门线程,也可以节省资源消耗。目前设计Server支持最多3个client
         的连接,针对3核的情况下,每个核可以有一个client。对server而言,需要关注每个client连接到了哪个socket,目
         前设计选择是使用RxCallbak,提供client当前连接的connection id。TxCallback没有使用,选择使用TxSema的方式来
         同步。对client端而言,可以都用semaphore同步。
    最后是port的概念有点变化,TCP/IP中port表示不同的server业务,同一个port号,不论IP是啥,都是指同一类业
         务,用IP:Port表示由哪个主机来提供该业务。本设计中,port不表示一个特定的业务,但是Port和CPU号两者一起,表
         示由哪个核提供特定的server,类似IP:Port。主要是,本设计基于响应及时性考虑,没有把socket做成动态分配和链
         表管理,预先分配足够多的连接控制块(socket管理结构),每个server 有3个,用于支持3个client的连接,每个
         client有一个。然后直接用port作为socket结构(连接控制块)数组的下标来快速引用。把一些处理放在ISR中做了,
         比如根据port找连接,不放在task处理,可以减少由于系统配置和任务优先级导致的时延不确定性,也不用创建一个
         工作线程来做搜索和分配。同一个port,如果cpu id不同,可以用于不同业务的话,可以减少资源的占用,而且,这
         种SoC 内通信,本来就是个封闭系统,不存在外部CPU协作通信以及动态配置,不强求port和业务类型绑定,对client
         来说只要找到server(CPU:Port)就满足设计要求,我们把CPU:Port 成为server ID或client ID。为方便应用配置使
         用,定义了两个枚举类型,server_id_t 和client_id_t,使用者只要在对应的CPU项下填写server/client名字就可以
         自动生成cpu:port 对,程序引用这个枚举常量。
         参见mb_ipc_port_cfg.h,以及mb_ipc_test.c中server / client task的创建示例。
    
    这套API因为是基于内存指针的数据交换,而不是数据COPY的方式,所以也有一个好处,没有常见的数据粘连问题,

    每次发送的数据包都是整个传输(类似UDP报文)。

    在 Router层之上,传输层通信已经屏蔽了mailbox的连接细节。在传输层之上Server/Client不关心运行的哪个CPU上,

    也就是server、client也可以运行在同一个CPU上,也可以运行在不同CPU上,支持非直连的CPU上APP 通信。

  • 主要API说明:

    1) u32 mb_ipc_socket(u8 port, void * rx_callback)
    综合了socket和bind 两个接口的工作,创建一个socket(内部预先创建,其实是通过port分配或指定一个,并

    创建相关管理资源,比如TxSema,如果rx_callbak不提供时,还创建RxSema)

    如果port为0,系统会分配一个client port。建议client也总是用固定的port,不要让系统分配。因为,由于多

    种原因client可能会意外停止运行,而设计中不检测连接保持,如果动态分配,下次client再次起来时,可能会 分配到一个不同的port,导致前一次client意外停止的连接还是继续存在(端口及资源没有释放)。如果总是用 同样的port,client下次连接时(比如reset后重连)发现同样的port已经有一个连接在了,server端就会内部重 置这个连接,恢复初始状态,然后继续重用这个连接。(注:最新版本已经把端口分配功能取消了,也就是port 为0会被认为参数错)

    2)int mb_ipc_connect(u32 handle, u8 dst_cpu, u8 dst_port, u32 time_out)
    连接到对应的server。所有API的time_out参数,如果提供的值小于600ms,那么内部会把它改为600ms。主要是

    考虑到flash擦除时间最大可能会到500-600ms左右,如果正在擦除flash,而超时时间设置的比较短,比如100ms, 那么可能会不必要的发送多个cmd。该值在代码中有宏可配。

    每个server最多可连3个client。(这个值可配置,如果系统不需要那么多client,可以减少到1或者2,以节省资

    源消耗。这个配置值适用所有的server/client,通常不建议改小,因为有的server可能需要比较多的client)

    3)int mb_ipc_close(u32 handle, u32 time_out)

    和server/client断开连接,通常client发起。

    4)int mb_ipc_send(u32 handle, u8 user_cmd, u8 * data_buff, u32 data_len, u32 time_out)

    如果只是发送一个命令,没有数据参数,可以data_buff=NULL,data_len=0。 如果没有命令,只有数据,那么user_cmd可以用除0xFF外的任意值,这个取决于通信双方约 定。(INVALID_USER_CMD_ID 设置为 0xFF,表示非法cmd id) 以上API,返回0,表示成功,小于0表示出错,其值就是错误码。 每次传输,最多只能传输0xFFFF 字节,也就是data_len 只有低16 bit 有效。

    5)int mb_ipc_recv(u32 handle, u8 * user_cmd, u8 * data_buff, u32 buff_len, u32 time_out)

    如果不关心cmd,或者没有user_cmd,那么 user_cmd可以=NULL。 小于0,表示出错,其值就是错误码。 > 0, 表示收到的数据长度。 =0,表示收到了0长度的数据包,可能有cmd。如果cmd != INVALID_USER_CMD_ID 就是收到了一个合法的user_cmd,否则就是出错了。

    每个API的返回码如下(第一个为0不是错误码):

    enum
    {
        MB_IPC_TX_OK = 0,
        /* error codes occurring in call side. */
        MB_IPC_TX_FAILED,
        MB_IPC_DISCONNECTED,
        MB_IPC_INVALID_PORT,
        MB_IPC_INVALID_HANDLE,
        MB_IPC_INVALID_STATE,
        MB_IPC_INVALID_PARAM,
        MB_IPC_NOT_INITED,
        MB_IPC_TX_TIMEOUT,
        MB_IPC_TX_BUSY,
        MB_IPC_NO_DATA,
        MB_IPC_RX_DATA_FAILED,
        /* base error code for router. */
        /* errors occuring in router layer of tx side or router cpu. */
        MB_IPC_ROUTE_BASE_FAILED = 0x20,
    
        /* base error code for API-implementation. */
        /* errors occuring in destination cpu/port API layer. */
        MB_IPC_API_BASE_FAILED = 0x30,
    };
    
    
    每层都有对应的错误码范围。
    
    以下是客户端的实现模型:
    如果mb_ipc_recv 收到数据长度不为0,需要一直接收。(直到返回的数据长度小于提供的缓冲区长度,

    此时说明数据接收完。或者根据mb_ipc_get_recv_data_len返回的长度接收指定长度的数据)

    示例代码可以参考flash_client.c 和mb_ipc_test.c (mb_ipc_test_client 该函数实现了一个客户端

    和server的交互流程)

    以下是服务器端的实现模型:
    和常规的socket API实现server相比,少了bind,listen,accept,这几个接口实现的功能都在MB_IPC

    模块内部实现了,包括connect,disconnect(客户端调mb_ipc_close)。在客户端mb_ipc_send时,服 务器端的rx-callback会收到通知,包括是哪个客户端在send。这种机制可以不用在server端为每个连接 创建单独的task,由一个server task管理所有client的连接和通信。Server端收到通知后,向MB_IPC 查 看是哪个连接(client),发生的事件(目前只支持 client 的send 事件),获取收到数据的长度,然 后调用recv获取数据,app处理数据后,用send应答客户端。

    示例代码可以参考flash_server.c 和mb_ipc_test.c (mb_ipc_test_svr1该函数实现了一个服务器task,

    rx-callback 的实现可以参照这些样本,client id 0~2)

    还有一个特别注意的地方是:用MB_IPC做核间通信时,如果在数据结构中传递了本核(源核)的内存指针给

    另外一个核(目的核)引用,在目的核开cache的情况下,都要对收到的指针进行flush_dcache,确保读到 的是最新的数据,参见第flash_server.c和flash_client.c中的代码,而在mb_ipc_test.c实现中,没有在 发送的数据内容中包含内存指针给对方读,所以不用flush_dcache(类似于传值,其实是MB_IPC内部对 mb_ipc_send中的data_buff内存指针进行了flush_dcache,确保App 调用mb_ipc_recv时收到的是最新的数据)。

  • MB_IPC 模块配置使用说明:

    本设计支持port号最多63个,从1-63,端口0保留,是个非法/无效的端口号。其中前15个保留给server,后

    面48个给client使用。每个server,最多支持3个client连接。 实际系统中通常不需要这么多端口和连接,可以根据需要配置,文件中有如下配置项:

    #define SERVER_PORT_NUM             4   // max reserved server PORT num supported by this design is 15 (1~15).
    
    #define MAX_CONNET_PER_SVR          3   // at most 3 clients could connect to one server. max support value is 4.
    
    #define CLIENT_PORT_NUM             12  // max client PORT num supported by this design is 48 (16~63).
    
    #define SERVER_PORT_MIN             1   // port 0 is invaid.
    #define CLIENT_PORT_MIN             16
    
    
    当前代码,这些配置项设置如上,每个CPU 最多支持4个server task,每个server task支持3个client连接,
                (对每个server,可以是每个CPU 有一个client)。
    这些配置项的值,对每个CPU 都一样,不支持任何配置项针对每个CPU不同值的设置,因为某个CPU上数值不
                同,其实也影响其他CPU 上的资源分配,为了减少复杂性,要求设置统一。(因为其他CPU可能要代为转发,
                需要有足够的资源。目前资源都是静态分配,不是根据连接数量动态分配)
    涉及跨核交互的API,都有一个超时参数,API内部对这个超时值有最小要求,为600ms。如果传入的参数值
                小于该要求值,那么超时时间会被更改为最小要求,这个值可配置。此外还有两个配置参数:重传次数,两
                次重传之间的间隔时间,如下:
    
    #define MB_IPC_CMD_TIMEOUT          600  //最新代码已经取消了这个最小超时时间值的要求,但是建议
                client端都用这个值,server端为了加快对client的响应,可以用较小的超时时间值,但是确实会碰到
                因flash擦除导致API超时的情况。具体示例可参考flash server 和Flash client的实现
    #define MB_IPC_RETRY_DELAY          2
    #define MB_IPC_RETRY_MAX            1
    
    考虑到核间通信还是比较可靠的,目前实际没有重传(传输次数为1)
    超时值最小要求选择600ms。主要是考虑到flash擦除时间最恶劣情况下可能会到500-600ms左右,如果正
                在擦除flash,而超时时间又比较短,比如100ms,那么可能会不必要的发送多个cmd。该值可根据flash的情
                况配置,但是通常不用修改,因为如果flash擦除比较快速,没到超时时间命令就已经完成执行。(由于Flash
                ctrl 没有Erase/Program 的suspend/resume 功能,导致Flash擦除时,整个系统不能执行任何指令,一旦擦
                除结束,立即就进行RTOS的tick补偿,导致许多API还没有机会执行就超时了。)
    
  • MB_IPC API使用示例

1)u32 mb_ipc_socket(u8 port, void * rx_callback)

    描述:
    综合了socket和bind 两个接口的工作,创建一个socket,并绑定到指定的端口号。如果port为0,系统会分配
                一个client port。建议client也总是用固定的port,不要让系统分配。因为,由于多种原因client可能会意外
                停止运行,                   而设计中不检测连接保持,如果动态分配,下次client再次起来时,可能会分配到一个
                不同的port,导致前一                    次client意外停止的连接还是继续存在(端口及资源没有释放)。如果总
                是用同样的port,client下次连接时(比如reset后重连)发现同样的port已经有一个连接在了,server端就
                会内部重置这个连接,恢复初始状态,然后继续重用这个连接。
    (注:最新版本已经把端口分配功能取消了,也就是port为0会被认为参数错)

    参数:port,
    ipc socket绑定的port口,(IPC socket是内部连接控制块的数据结构),
    rx_callback,
    socket事件通知回调函数,它被中断处理函数调用,所以不能在回调函数中做业务处理,只能做一些通知处理。
                通常server因为支持多个连接,所以需要该回调函数,对client端,该参数建议为NULL。

    返回值:成功时,为socket句柄,后续的socket API 都用该句柄作为参数,以引用内部的ipc socket数据结构。
    失败时,返回0。
    该函数的原型如下:
    u32 svr_rx_callback(u32 handle, u32 connect_id)
    handle,是连接socket的句柄,
    connect_id,连接到server的某个client,0~(MAX_CONNET_PER_SVR -1)。

::

    server示例:
    static u32 svr_rx_callback(u32 handle, u32 connect_id)
    {
        u32  connect_flag;

        if(connect_id >= SVR_CONNECT_MAX)
                return 0;
        connect_flag = 0x01 << connect_id;
        rtos_set_event_ex(&svr_event, connect_flag);
        return 0;
    }

    void svr_task(void *param)
    {
        ……
        handle = mb_ipc_socket (IPC_GET_ID_PORT(TEST_SERVER), svr_rx_callback);

        if(handle == 0)
        {
                BK_LOGE("ipc_svr", "create_socket failed.\r\n");
        }
        ……
    }

    Client示例:
    void client_task(void * param)
    {
        ……
        u32 handle = mb_ipc_socket(IPC_GET_ID_PORT(TEST_CLIENT), NULL);
        if(handle == 0)
        {
                bk_printf("client create socket failed\r\n");
        }
        ……
    }

2)int mb_ipc_connect(u32 handle, u8 dst_cpu, u8 dst_port, u32 time_out)

::

    描述:
    用于client连接到server,server由dst_cpu:dst_port 指定。
    参数:handle,前一个函数创建的socket 的句柄。
    dst_cpu:dst_port,指定server在哪个CPU上哪个端口。
    time_out,超时时间。在该时间内不能和server建立连接则出错。
    返回值:成功时,为0,失败时 < 0,并且该负值就是错误码。

    示例:
        int ret_val = mb_ipc_connect(handle, IPC_GET_ID_CPU(TEST_SERVER), IPC_GET_ID_PORT(TEST_SERVER), 500);
        if(ret_val != 0)
        {
                bk_printf("client connect failed %d\r\n", ret_val);
        }

    为了方便client、server分配端口号,定义了两个枚举,只要在里面添加client/server ID就可以
                分配端口号,同时决定了server或client运行在哪个CPU上。

    typedef enum
    {
        //   servers resided in cpu0.
        CPU0_SERVER_ID_START = IPC_SVR_ID_START(0),
        TEST_SERVER,    // CPU0上有一个server,端口号为1。

        //   servers resided in cpu1.
        CPU1_SERVER_ID_START = IPC_SVR_ID_START(1),

        //   servers resided in cpu2.
        CPU2_SERVER_ID_START = IPC_SVR_ID_START(2),

    } mb_ipc_svr_id_t;

    typedef enum
    {
    #if CONFIG_SYS_CPU0
        //   clients resided in cpu0
        CPU0_CLIENT_ID_START = IPC_CLIENT_ID_START(0),
    #endif

    #if CONFIG_SYS_CPU1
        //   clients resided in cpu1
        CPU1_CLIENT_ID_START = IPC_CLIENT_ID_START(1),
        TEST_CLIENT,    // CPU1上有个client,,端口号为16。
    #endif

    #if CONFIG_SYS_CPU2
        //   clients resided in cpu2
        CPU2_CLIENT_ID_START = IPC_CLIENT_ID_START(2),
        TEST_CLIENT,    // CPU2上有个client,,端口号为16。
    #endif

    } mb_ipc_client_id_t;

    程序中使用这些枚举ID以及相应的宏,就可以获得CPU:PORT,如mb_ipc_connect示例所示。

3)int mb_ipc_close(u32 handle, u32 time_out)
描述:
用于client/server关闭handle指定的连接,该接口通常由client调用,server基本不会主动关闭连接。
        服务程序退出时,要调用mb_ipc_server_close以关闭所有的连接。
参数:handle,前一个函数创建的socket 的句柄。
time_out,超时时间。在该时间内不能关闭连接则出错。
返回值:成功时,为0,失败时 < 0,并且该负值就是错误码。

示例:

mb_ipc_close(handle, 500);


4)int mb_ipc_send(u32 handle, u8 user_cmd, u8 * data_buff, u32 data_len, u32 time_out)
描述:
用于client/server数据发送。发送函数有一个和socket send接口不同的参数user_cmd,方便没有数据
        时可以仅发送一个命令,data_buff=NULL,data_len=0。
        因为业务层主要传输数据指针供另一个核读取数据,而不是数据本身,所以经常有一些握手,不一定需
        要额外的命令参数数据,因此在该API中添加了这个user_cmd参数。如果不需要这个参数 ,可以用除0xFF
        外的任意值,这个取决于通信双方约定。(INVALID_USER_CMD_ID 设置为 0xFF,表示非法user_cmd id)

参数:handle,mb_ipc_socket函数创建的socket 的句柄。
user_cmd,如上所述,
data_buff,传输的数据。
data_len,数据长度,低16 bit 有效,所以最多0xFFFF 字节。
time_out,超时时间。在该时间内不能发送成功则出错。
返回值:成功时,为0,失败时 < 0,并且该负值就是错误码。

示例:

        ret_val = mb_ipc_send(handle, FLASH_CMD_READ, (u8 *)cmd_buff, sizeof(flash_cmd_t), FLASH_OPERATE_TIMEOUT);

        if(ret_val != 0)
        {
                (*read_buff) ^= 0x01;   // make crc not match
                os_free(read_buff);
                TRACE_E(TAG, "%s @%d, 0x%x send failed!\r\n", __FUNCTION__, __LINE__, handle);
                return;
        }


5)int mb_ipc_recv(u32 handle, u8 * user_cmd, u8 * data_buff, u32 buff_len, u32 time_out)
描述:
用于client/server接收数据。user_cmd参数的描述参见mb_ipc_send接口,如果不关心cmd,
        或者没有user_cmd,那么 user_cmd可以=NULL。
        该接口一个特别重要的地方需要注意:只有把对方发送的数据都读取了,内部接收管理模块
        才会向发送方回应接收成功,然后发送方认为发送成功。
        如果接收方没把发送方发送的data_len长度的数据读走,发送方一直等待,直到超时。
        每次recv返回数据并处理后,都要再次调用recv,参数timeout为0,直到数据读完。

参数:
        handle,mb_ipc_socket函数创建的socket 的句柄。
user_cmd,用于接收cmd的缓冲,可以为NULL。
data_buff,存储接收数据的缓冲区。可以为NULL,如果为NULL,就是清空所有接收到的
        数据,发送方会收到成功通知,并可继续发送数据。
buff_len,缓冲区长度。可以为0,如果为0,就是清空所有接收到的数据,发送方会收
        到成功通知,并可继续发送数据。
time_out,超时时间。对client来说在该时间内不能接收成功则出错。
该参数对server无效,因为server用了rx_callback,有数据到达才会去调用该recv函数。
返回值:> 0, 表示收到的数据长度。
=0,表示数据接收完了。
返回值 >= 0时,如果user_cmd != NULL且 cmd != INVALID_USER_CMD_ID,就是收到了
        一个合法的user_cmd。
失败时 < 0,并且该负值就是错误码,此时user_cmd内容无效。
返回值0需要特别关注处理,对sever可能收到了一个0长度的数据包,也可能是没收到任
        何数据,此时,可以通过user_cmd判断,如果cmd != INVALID_USER_CMD_ID就是收到了
        一个0长度参数数据的命令包,否则就是没收到数据。
对client来说,如果cmd != INVALID_USER_CMD_ID就是收到了一个0字节数据的命令包,
        否则就是没收到数据(超时了)。
为了帮助区分则两种情况,user_cmd参数建议不要为NULL。
这个函数的特殊之处是:在函数返回值和 user_cmd两处都有数据返回,只有两个地方
        返回的值都失败时,才是API 真正的失败。

server示例:
                int   rem_len = mb_ipc_get_recv_data_len(handle);

                bk_printf("==recv 0x%x, %d bytes.\r\n", handle, rem_len);

                u8       data_buff[32];
                int      read_len = 0;

                if(rem_len == 0)
                {
                        u8       user_cmd;
                        read_len = mb_ipc_recv(handle, &user_cmd, data_buff, 0, 0);
                        if(read_len < 0)
                        {
                                bk_printf("==recv cmd failed! %d\r\n", read_len);
                        }
                        else
                        {
                                bk_printf("==recv cmd= %d\r\n", user_cmd);
                        }
                }

                while(rem_len > 0)
                {
                        read_len = mb_ipc_recv(handle, NULL, data_buff, sizeof(data_buff), 0);

                        if(read_len < 0)
                        {
                                bk_printf("==recv failed! %d\r\n", read_len);
                                break;
                        }
                        else if(read_len > 0)
                        {
                                rem_len -= read_len;
                                bk_printf("==> recv %d bytes\r\n", read_len);
                                for(int i = 0; i < read_len; i++)
                                {
                                        BK_LOG_RAW("%02x ", data_buff[i]);
                                }

                                if(read_len < sizeof(data_buff))
                                {
                                //      bk_printf("recv complete!\r\n");
                                        break;
                                }
                        }
                        else
                        {
                        //      bk_printf("recv failed!\r\n");
                                break;
                        }

                }


Client示例:

                u8       data_buff[32];
                int      read_len = 0;
                u8       user_cmd;

                u32      time_out = 200;

                do
                {
                        read_len = mb_ipc_recv(handle, &user_cmd, data_buff, sizeof(data_buff), time_out);

                        if(read_len < 0)
                        {
                                bk_printf("--recv failed! %d\r\n", read_len);
                                break;
                        }
                        else if(read_len == 0)
                        {
                                // if it is the first loop!
                                if((user_cmd == INVALID_USER_CMD_ID) && (time_out != 0))
                                {
                                        bk_printf("--recv cmd failed!\r\n");
                                }

                                bk_printf("--recv complete! cmd= %d\r\n", user_cmd);
                                break;
                        }
                        else // (read_len > 0)
                        {
                                bk_printf("--recv %d bytes\r\n", read_len);
                                for(int i = 0; i < read_len; i++)
                                {
                                        BK_LOG_RAW("%02x ", data_buff[i]);
                                }
                        }

                        time_out = 0;   // time_out = 0 for subsequent loops!

                } while(read_len == sizeof(data_buff));


6)int mb_ipc_get_recv_data_len(u32 handle)
描述:
获取当前接收到的待处理数据,该接口通常由server调用。
参数:handle,mb_ipc_socket函数创建的socket 的句柄。
返回值:成功时,>=0,指示有多少数据待接收处理。
失败时 < 0,并且该负值就是错误码。
对于返回值为0的情况,需要特别关注,建议后续recv时用user_cmd 参数来帮助区
        分是否有数据收到。参见mb_ipc_recv描述。

示例:

参见 mb_ipc_recv的server示例。

7)int mb_ipc_get_recv_event(u32 handle, u32 * event_flag)
描述:
获取当前收到的事件(IPC命令),该接口通常由server调用。Client通常不需要
        提供rx_callback,都是同步API。
参数:handle,mb_ipc_socket函数创建的socket 的句柄。
     event_flag, 用于保存收到的命令码。
返回值:成功时,=0,event_flag 中包含当前收到的IPC 命令。
失败时 < 0,并且该负值就是错误码。

示例:
        u32  cmd_id;
        int ret_val = mb_ipc_get_recv_event(handle, &cmd_id);
        if(ret_val != 0)  // failed
        {
                bk_printf("get evt fail %x %d.\r\n", handle, ret_val);
                return;
        }

        if(cmd_id > MB_IPC_CMD_MAX)
        {
                bk_printf("cmd-id error %d.\r\n", cmd_id);
                return;
        }

        if(cmd_id == MB_IPC_DISCONNECT_CMD)
        {
                bk_printf("disconnect 0x%x.\r\n", handle);
        }
        else if(cmd_id == MB_IPC_CONNECT_CMD)
        {
                bk_printf("connect 0x%x.\r\n", handle);
        }
        else if(cmd_id == MB_IPC_SEND_CMD)
        {
                …………
        }



8)int mb_ipc_server_close(u32 handle, u32 time_out)
参见 mb_ipc_close 描述,该API只能被server调用,用于关闭所有的连接。
示例:
        mb_ipc_server_close(handle, 1000);

9)u32 mb_ipc_server_get_connect_handle(u32 handle, u32 connect_id)
描述:
该接口通常由server调用,根据rx_callback收到的connect_id 找到和server连
        接的对应的socket句柄,类似socket接口中,由listen socket通过accept生成
        connect socket。
参数:handle,mb_ipc_socket函数创建的server socket 的句柄。
     connect_id, rx_callback中收到的连接指示,说明client来自哪条连接。
返回值:返回和client连接的socket句柄,
失败时,=0,非法句柄,如果传入的handle 或者connect_id不合法,那么就会返
        回非法句柄0。
成功时 > 0,返回连接句柄,用于后续和client通信。

示例::
                connect_handle = mb_ipc_server_get_connect_handle(handle, i);


10)异常码解释。
// server端需要处理这些命令,client端不关心。
enum
{
        MB_IPC_CONNECT_CMD = 0,
        MB_IPC_DISCONNECT_CMD,

        MB_IPC_SEND_CMD,

        MB_IPC_CMD_MAX  = 0x7F,  /* cmd id can NOT great than 0x7F. */
};

enum
{
        MB_IPC_TX_OK = 0,      // 发送成功,不是错误码。

        /* error codes for occuring in tx side. */
        MB_IPC_TX_FAILED,      // mailbox逻辑通道发送失败。
        MB_IPC_DISCONNECTED,  // 数据在等待发送过程中,链路断开,收到了disconnect命令。
        MB_IPC_INVALID_PORT,   // 由mb_ipc_connect返回,参数port不是一个server port。
        MB_IPC_INVALID_HANDLE,
// 任一个API 都可能返回,
// 通过handle 参数没法找到对应的socket连接时,会返回该异常码。
// 可能连接已经关闭,可能handle值本来就是一个异常值,
// 可能不是由mb_ipc_socket返回的句柄。

        MB_IPC_INVALID_STATE,
// 状态非法,对connect来说,就是已经建立了连接,对该句柄再次调用connect。
// 对其他API来说,就是尚未建立连接,就进行收发API调用。

        MB_IPC_INVALID_PARAM,  // mb_ipc_get_recv_event返回,event_flag == NULL
        MB_IPC_NOT_INITED,  // 句柄没有通过mb_ipc_socket初始化,或者已经mb_ipc_close。
        MB_IPC_TX_TIMEOUT,
// 由send,connect,close返回,相关动作未能在指定时间内完成。
// client 调用recv 超时不会返回该异常码,而是返回0,表示没数据收到。
// 参见recv 描述。

        MB_IPC_TX_BUSY,  // 由mb_ipc_send返回,表示有一个send正在进行中,尚未收到应答
        MB_IPC_NO_DATA,  // 由mb_ipc_get_recv_data_len返回,表示当前没有收到数据包。

        MB_IPC_RX_DATA_FAILED,
// 由mb_ipc_recv返回,表示正在处理接收到的cmd_data_buff内存中的内容时,
// 发送方更改了cmd_data_buff内存中的数据。
// 可能是由于超时,发送方释放了内存。

        /* base error code for router. */
        /* errors occuring in router layer of tx side or router cpu. */
        MB_IPC_ROUTE_BASE_FAILED = 0x20,
// 发送过程中出错,mailbox逻辑通道发送成功后,
// 送到接收方App-impl层前的路由处理出错,APP-impl 层尚未收到数据。
// 具体错误码,参见  IPC_ROUTE_XXX  错误描述。

        /* base error code for API-implementation. */
        /* errors occuring in destination cpu/port API layer. */
        MB_IPC_API_BASE_FAILED = 0x30,
// App-impl 处理时出错。具体错误码,参见  IPC_API_IMPL_XXX  错误描述
};


enum
{
        /* 4 bits for route status, must be in range 0 ~ 15. */
        IPC_ROUTE_STATUS_OK = 0x00,
        IPC_ROUTE_QUEUE_FULL,    // 发送队列满,通常是因为mailbox逻辑通道出问题。
        IPC_ROUTE_UNREACHABLE,
// 找不到目标CPU对应的malbox逻辑通道
// 或者找不到相应的socket连接,数据没法发送到接收方。
        IPC_ROUTE_UNSOLICITED_RSP,
// 发送方已经由于超时退出发送状态了,再收到接收方的应答包,
// 导致该rsp无效,只能丢弃。

        IPC_ROUTE_UNMATCHED_RSP,
// 发送方已经由于超时退出后再次发送了新的命令包,
// 此时再收到接收方对前一个包的应答包,应答无效,应答包丢弃。
        IPC_ROUTE_RX_BUSY,
// 接收方还在处理前一个数据包,发送方又发了一个新的数据包给接收方。
//(对发送方,可能前一个数据包已经超时) 。
};

enum
{
        /* 4 bits for api-implementation status, must be in range 0 ~ 15. */
        IPC_API_IMPL_STATUS_OK = 0x00,
        IPC_API_IMPL_NOT_INITED,   // 接收方在数据处理时,socket连接被关闭
        IPC_API_IMPL_RX_BUSY,
// 意义同IPC_ROUTE_RX_BUSY,该错误码定义了,发送方不会收到该错误码,
// 因为还没交接收方的APP-impl层处理。
        IPC_API_IMPL_RX_NOT_CONNECT,
// 接收方在数据处理时,socket连接已经被关闭了。
        IPC_API_IMPL_RX_DATA_FAILED,
// 意义同MB_IPC_RX_DATA_FAILED,但是该错误码是给发送方的,
// 发送方通常也不会收到该错误码,因为此时发方应该已经退出发送状态了。
};

4 接口类型3:MB_UART 使用说明

  • MB_UART 模型:

    模拟出了一个带自动流控的UART。

  • MB_UART API说明

    默认实现了两个MB_UART,如果需要更多通路,可以根据MB_UART0的设置,再去
                配置相应MB_UART。如果只是需要一个MB_UART,可以在此删除MB_UART1,或者用配置宏开关。
    enum
    {
        MB_UART0 = 0,
        MB_UART1,
        MB_UART_MAX,
    };
    
    MB_UART的接收状态:
    #define MB_UART_STATUS_OVF                  0x01
    #define MB_UART_STATUS_PARITY_ERR   0x02
    typedef void (* mb_uart_isr_t)(void * param);
    
1) bk_mb_uart_dev_init
/**
 * @brief         initialize the mailbox UART device.
 *
 * Initialize the mailbox-emulated UART.
 *
 * @id     mailbox uart ID (MB_UART0, MB_UART1).
 *
 * @return
 *  - BK_OK: initialized success.
 *  - other: failed, fail code.
 */
bk_err_t bk_mb_uart_dev_init(u8 id);

2) bk_mb_uart_dev_deinit
/**
 * @brief         de-initialize the mailbox UART device.
 *
 * De-initialize the mailbox-emulated UART.
 *
 * @id     mailbox uart ID (MB_UART0, MB_UART1).
 *
 * @return
 *  - BK_OK: initialized success.
 *  - other: failed, fail code.
 */
bk_err_t bk_mb_uart_dev_deinit(u8 id);

3) bk_mb_uart_register_rx_isr
/**
 * @brief         register rx ISR callback to the mailbox UART device.
 *
 *
 * @id     mailbox uart ID (MB_UART0, MB_UART1).
 *
 * @isr    rx isr, prototype is void (* mb_uart_isr_t)(void * param)
 *
 * @param  will be passed to rx isr callback.
 *
 * @attention   isr callback is called in interrupt disabled context, when received data.
 *
 * @return
 *  - BK_OK: success.
 *  - other: failed, fail code.
 */
bk_err_t bk_mb_uart_register_rx_isr(u8 id, mb_uart_isr_t isr, void *param);

4) bk_mb_uart_register_tx_isr
/**
 * @brief         register tx ISR callback to the mailbox UART device.
 *
 *
 * @id     mailbox uart ID (MB_UART0, MB_UART1).
 *
 * @isr    tx isr, prototype is void (* mb_uart_isr_t)(void * param)
 *
 * @param  will be passed to tx isr callback.
 *
 * @attention   isr callback is called in interrupt disabled context, after sent cmpelete.
 *
 * @return
 *  - BK_OK: success.
 *  - other: failed, fail code.
 */
bk_err_t bk_mb_uart_register_tx_isr(u8 id, mb_uart_isr_t isr, void *param);

5) bk_mb_uart_write_ready
/**
 * @brief         get free FIFO space in UART device.
 *
 * @id     mailbox uart ID (MB_UART0, MB_UART1).
 *
 * @return
 *  - free FIFO space size in byte.
 */
u16 bk_mb_uart_write_ready(u8 id);

6) bk_mb_uart_read_ready
/**
 * @brief         get data bytes in FIFO of UART device.
 *
 * @id     mailbox uart ID (MB_UART0, MB_UART1).
 *
 * @return
 *  - data bytes ready in mailbox-UART FIFO.
 */
u16 bk_mb_uart_read_ready(u8 id);

7) bk_mb_uart_write_byte
/**
 * @brief         send one byte data via UART device.
 *
 * @id     mailbox uart ID (MB_UART0, MB_UART1).
 * @data   data byte to be sent.
 *
 * @return
 *  - data bytes sent via mailbox-UART (1 or 0).
 */
u16 bk_mb_uart_write_byte(u8 id, u8 data);

        8) bk_mb_uart_write
/**
 * @brief         send data in buffer via UART device.
 *
 * @id     mailbox uart ID (MB_UART0, MB_UART1).
 * @data_buf   data to be sent.
 * @data_len   data bytes in buffer.
 *
 * @return
 *  - data bytes sent via mailbox-UART (0 ~ data_len).
 */
u16 bk_mb_uart_write(u8 id, u8 *data_buf, u16 data_len);

        9) bk_mb_uart_read_byte
/**
 * @brief         receive one byte data via UART device.
 *
 * @id     mailbox uart ID (MB_UART0, MB_UART1).
 * @data   data buffer to receive one byte.
 *
 * @return
 *  - data bytes received via mailbox-UART (1 or 0).
 */
u16 bk_mb_uart_read_byte(u8 id, u8 * data);

10) bk_mb_uart_read
/**
 * @brief         receive data via UART device.
 *
 * @id     mailbox uart ID (MB_UART0, MB_UART1).
 * @data_buf   used to receive data.
 * @buf_len    buffer length.
 *
 * @return
 *  - data bytes received via mailbox-UART (0 ~ buf_len).
 */
u16 bk_mb_uart_read(u8 id, u8 *data_buf, u16 buf_len);

11) bk_mb_uart_is_tx_over
/**
 * @brief         check whether data has been flushed out to UART device.
 *
 * @id     mailbox uart ID (MB_UART0, MB_UART1).
 *
 * @return
 *  - 1 : no data is pending in Tx FIFO.
 *  - 0 : there are data pending in Tx FIFO.
 */
bool bk_mb_uart_is_tx_over(u8 id);

12) bk_mb_uart_dump
/**
 * @brief         flush data in buffer via UART device in sync mode.
 *
 * @id     mailbox uart ID (MB_UART0, MB_UART1).
 * @data_buf   data to be sent.
 * @data_len   data bytes in buffer.
 *
 * @attention   this API is called in interrupt disabled context.
 *
 * @return
 *  - BK_OK: success.
 *  - other: failed, fail code.
 */
bk_err_t bk_mb_uart_dump(u8 id, u8 *data_buf, u16 data_len);

该API用于特别目的,客户那边不建议使用。一旦调用该API,API就认为系统处于不
        正常状态,会关中断,通过轮询方式在该UART上发送数据,发送方也不依赖于中断回
        调的通知,而是在一个特定内存上握手通讯,交换状态。

13)  bk_mb_uart_get_status
/**
 * @brief         get status of UART device.
 *
 * @id     mailbox uart ID (MB_UART0, MB_UART1).
 *
 * @return
 *  - uart rx/tx status (0, MB_UART_STATUS_OVF, MB_UART_STATUS_PARITY_ERR, or bitwise of these 2 status.).
 */
u16 bk_mb_uart_get_status(u8 id);
  • 使用示例

    1)从接口到行为,这个MB_UART都类似于常规的UART设备,除了一点:MB_UART基于

    mailbox的逻辑通道,而逻辑通道的通讯依赖于mailbox硬件中断的通知,因此MB_UART的 实现依赖于系统的中断和mailbox硬件中断,也就是在系统关中断情况下MB_UART无法工作。 (尽管MB_UART可以轮询方式使用,那也只是说可以不向MB_UART注册Tx/Rx ISR回调函数, 而不是真正意义上的关中断轮询方式。)在系统启动时候,或者业务模块启动初始化时 候,先初始化MB_UART设备

    bk_mb_uart_dev_init(MB_UART0);
    

    2)如果工作在中断通知方式下,在业务task的初始化模块中向MB_UART注册Tx/Rx ISR回调函数:

    struct mb_uart_rx_param
    {
        u8     uart_id;
        beken_semaphore_t       rx_sema
    } dev_rx_param;
    struct mb_uart_tx_param
    {
        u8     uart_id;
        beken_semaphore_t       tx_sema
    } dev_tx_param;
    
    bk_mb_uart_register_rx_isr(MB_UART0, debug_mb_uart_rx_callback, &dev_rx_param);
    bk_mb_uart_register_tx_isr(MB_UART0, debug_mb_uart_tx_callback, &dev_tx_param);
    

    3)回调函数的原型是:

    void (* mb_uart_isr)(void * param)
    
    static void debug_mb_uart_rx_callback(void * param)
    {
        struct mb_uart_param * mbu_para = (struct mb_uart_rx_param *)param;
    
        rtos_set_semaphore(&mbu_para-> rx_sema);  // 通知数据到达
    
        BK_LOGI(TAG," MB_Uart:%d Rx OK\r\n", mbu_para->uart_id);
    }
    
    static void debug_mb_uart_tx_callback(void * param)
    {
        struct mb_uart_param * mbu_para = (struct mb_uart_tx_param *)param;
    
        rtos_set_semaphore(&mbu_para-> tx_sema);  // 通知数据发送完成
    
        BK_LOGI(TAG," MB_Uart:%d Tx OK\r\n", mbu_para->uart_id);
    }
    

    业务task 收到数据到达通知后用以下API获知MB_UART状态并接收数据:

    rtos_get_semaphore(&mbu_para-> rx_sema, BEKEN_WAIT_FOREVER);
    
    u8          test_buf[80];
    temp = bk_mb_uart_get_status(uart_id);
    BK_LOGI(TAG,"m_uart%d state:%d \r\n", uart_id, temp);
    
    temp = bk_mb_uart_read_ready(uart_id);
    BK_LOGI(TAG,"m_uart%d RxFIFO data:%d bytes\r\n", uart_id, temp);
    
    while(temp > 0)
    {
        rd_len = bk_mb_uart_read(uart_id, test_buf, sizeof(test_buf));
    
        BK_LOGI(TAG,"rx:%d bytes\r\n", rd_len);
        // BK_LOGI(TAG,"rx:%s \r\n", test_buf);
    
        for(i = 0; i < rd_len; )
        {
                if(rd_len - i >= 8)
                {
                        os_printf(" %x %x %x %x %x %x %x %x\r\n", test_buf[i], test_buf[i+1],test_buf[i+2],test_buf[i+3],
                                test_buf[i+4],test_buf[i+5],test_buf[i+6],test_buf[i+7]);
                        i+= 8;
                }
                else
                {
                        os_printf(" %x", test_buf[i]);
                        i++;
                }
        }
        os_printf("\r\n");
    
        // temp -= rd_len;
        temp = bk_mb_uart_read_ready(uart_id);
    }
    

    业务task 收到数据发送完成通知后用以下API继续数据发送:

rtos_get_semaphore(&mbu_para-> tx_sema, BEKEN_WAIT_FOREVER);
do
{
        if (bk_mb_uart_write_ready(uart_id) == 0)  // Tx FIFO has space for new data?
                rtos_delay_milliseconds(2);

        temp = bk_mb_uart_write(uart_id, data_buf, data_len);

        BK_LOGI(TAG,"m_uart%d tx:%d bytes \r\n", uart_id, temp);
        data_len -= temp;

} while(data_len > 0);


如果不注册Tx/Rx ISR回调,就不会有semaphore通知,MB_UART工作在轮询方式,不建议使用。
  • 配置MB_UART

    如果需要定义新的MB_UART,需要向在mailbox_channel.h 中定义一条新的逻辑通道。如果是

    CPU0 和 CPU1 之间通讯,那么需要在CPU0和CPU1 之间同时定义,而且确保在两个CPU中,定 义的逻辑通道顺序是一样的(也就是物理通道不同,逻辑通道序号一样)。

/* ======================================================================================= */
/* ==========================  CPU0 --> Target_CPUs mailbox.   =========================== */
/* ======================================================================================= */
#if CONFIG_SYS_CPU0

#define SELF_CPU                MAILBOX_CPU0


/* the BIGGER the value, the LOWER the channel priority. */
enum
{
        /* CPU0 --> CPU1 */
        CP1_MB_LOG_CHNL_START   = CPX_LOG_CHNL_START(SELF_CPU, MAILBOX_CPU1),
        MB_CHNL_HW_CTRL         = CP1_MB_LOG_CHNL_START,
        MB_CHNL_LOG,   /* MB_CHNL_LOG should be the LAST one. LOWEST priority. */
        MB_CHNL_COM,   /* MB_CHNL_COM is default mailbox channel used for audio and video */
        MB_CHNL_LCD_QSPI,
        MB_CHNL_DATA,
        MB_CHNL_UART1,
        MB_CHNL_UART2,
        CP1_MB_LOG_CHNL_END,
        CP1_MB_LOG_CHNL_MAX = (CP1_MB_LOG_CHNL_START + LOG_CHNL_ID_MASK), // max 16 channels.
};

/* ======================================================================================= */
/* ==========================  CPU1 --> Target_CPUs mailbox.   =========================== */
/* ======================================================================================= */
#if CONFIG_SYS_CPU1

#define SELF_CPU                MAILBOX_CPU1

enum
{
        /* CPU1 --> CPU0 */
        CP0_MB_LOG_CHNL_START   = CPX_LOG_CHNL_START(SELF_CPU, MAILBOX_CPU0),
        MB_CHNL_HW_CTRL         = CP0_MB_LOG_CHNL_START,
        MB_CHNL_LOG,   /* MB_CHNL_LOG should be the LAST one. LOWEST priority. */
        MB_CHNL_COM,   /* MB_CHNL_COM is default mailbox channel used for audio and video */
        MB_CHNL_LCD_QSPI,
        MB_CHNL_DATA,
        MB_CHNL_UART1,
        MB_CHNL_UART2,
        CP0_MB_LOG_CHNL_END,
        CP0_MB_LOG_CHNL_MAX = (CP0_MB_LOG_CHNL_START + LOG_CHNL_ID_MASK), // max 16 channels.
};

在mb_uart_driver.h中定义uart id:
enum
{
        MB_UART0 = 0,
        MB_UART1,
        MB_UART_MAX,
};

在mb_uart_driver.c 中绑定 uart id 和逻辑通道的关系。
static const u8                 mb_uart_chnl_id[MB_UART_MAX] = {MB_CHNL_UART1, MB_CHNL_UART2};

在xchg buffer 中为该UART 分配Tx/Rx FIFO, 在mb_chnl_buffer.c中:

static const chnl_buff_cfg_t   chnl_buf_cfg_tbl[] =
{
        { MB_CHNL_HW_CTRL,  MB_CHNL_CTRL_BUFF_LEN },
        { MB_CHNL_LOG,      MB_CHNL_LOG_BUFF_LEN  },
        { MB_CHNL_UART1,    MB_CHNL_UART_BUFF_LEN },
        { MB_CHNL_UART2,    MB_CHNL_UART_BUFF_LEN },
};
按照上述配置,xchg buffer中占用的内存大小是:
(MB_CHNL_CTRL_BUFF_LEN + MB_CHNL_LOG_BUFF_LEN + MB_CHNL_UART_BUFF_LEN + MB_CHNL_UART_BUFF_LEN )x 2.
因为每个通道都有Tx/Rx,所以需要 乘以2。

每个UART在xchg buffer 中占用的内存长度(FIFO len),可以在mb_chnl_buffer.h 中配置。

#define MB_CHNL_CTRL_BUFF_LEN           32
#define MB_CHNL_LOG_BUFF_LEN            144
#define MB_CHNL_UART_BUFF_LEN           128

如果需要删除多余的MB_UART,参照上述流程做删除操作。