概述
Common Mailbox Framework主要用作soc和各个subsystem的通信框架 。以Qcom平台为例,这些subsystem包括AOP,ADSP以及CDSP等等。
主要涉及以下概念:
- 客户端(mbox_client): 主要对各个dev,注册信息处理的回调函数。
- 通道(mbox_chan): 通道用来链接客户端和控制器,以及管理message。
- 控制器(mbox_controller): 为注册到控制器上的channels提供服务。以及实现通信的OPS。
结构体
Client结构体
struct mbox_client {
struct device *dev;
bool tx_block;
unsigned long tx_tout;
bool knows_txdone;
void (*rx_callback)(struct mbox_client *cl, void *mssg);
void (*tx_prepare)(struct mbox_client *cl, void *mssg);
void (*tx_done)(struct mbox_client *cl, void *mssg, int r);
};
- dev: client设备
- tx_block: 如果在传输数据完成之前,mbox_send_message应阻塞。
- tx_tout: tx的timeout,TX发生故障前的最大块周期(ms)
- knows_txdone: 如果客户端可以运行TX状态机。通常,如果客户端接收到一些用于传输的ACK数据包。如果控制器已具有TX_Done/RTR IRQ,则不使用。
- rx_callback: 原子回调以向client提供接收到的数据。
- tx_prepare: 原子回调,要求client在启动传输之前准备有效负载(如果需要)。
- tx_done: 原子回调通知client数据传输完成。
Channel结构体
struct mbox_chan {
struct mbox_controller *mbox;
unsigned txdone_method;
struct mbox_client *cl;
struct completion tx_complete;
void *active_req;
unsigned msg_count, msg_free;
void *msg_data[MBOX_TX_QUEUE_LEN];
spinlock_t lock; /* Serialise access to the channel */
void *con_priv;
};
#define TXDONE_BY_IRQ BIT(0) /* controller has remote RTR irq */
#define TXDONE_BY_POLL BIT(1) /* controller can read status of last TX */
#define TXDONE_BY_ACK BIT(2) /* S/W ACK recevied by Client ticks the TX */
- mbox:指向此channel的 parent/provider的指针
- txdone_method:检测API选择的TXDone的方法,TXDONE_BY_IRQ,TXDONE_BY_POLL和TXDONE_BY_ACK三选一。
- cl:指向当前channel当前所有者的指针,即mbox_client。
- tx_complete:channel的tx compelete。
- msg_data:要发送的msg循环存放到msg_data数组中,等待发送。
- msg_count:在msg_data数组中,当前需要发送的msg的个数。
- msg_free:在msg_data数组中,最新存的msg的索引(循环存放)。
- active_req:指向msg_data数组正在发送的msg。
- con_priv:private data指针
Controller结构体
struct mbox_controller {
struct device *dev;
const struct mbox_chan_ops *ops;
struct mbox_chan *chans;
int num_chans;
bool txdone_irq;
bool txdone_poll;
unsigned txpoll_period;
struct mbox_chan *(*of_xlate)(struct mbox_controller *mbox,
const struct of_phandle_args *sp);
/* Internal to API */
struct hrtimer poll_hrt;
struct list_head node;
};
- dev:指向设备
- ops:所有channel的ops
- chans:channels的数组。
- num_chans:channels数组中的个数。
- txdone_irq: 指示控制器是否可以在远程读取最后传输的数据时向,通过TX ACK irq向API报告。txdone_irq对应TXDONE_BY_IRQ。
- txdone_poll:如果控制器可以读取但不能报告TX完成。例如,一些寄存器显示TX状态,但没有出现中断。如果设置了“txdone_irq”,则忽略。txdone_poll对应TXDONE_BY_POLL。
- txpoll_period:如果“txdone_poll”有效,API将在这些毫秒之后轮询最后一个TX的状态。
- of_xlate: 解析DT中的controller驱动程序特定的channel映射。
- poll_hrt: hr计时器用于轮询所有channels上的TXDONE。
- node:挂接到controllers列表mbox_cons中.
mailbox channels的ops
struct mbox_chan_ops {
int (*send_data)(struct mbox_chan *chan, void *data);
int (*flush)(struct mbox_chan *chan, unsigned long timeout);
int (*startup)(struct mbox_chan *chan);
void (*shutdown)(struct mbox_chan *chan);
bool (*last_tx_done)(struct mbox_chan *chan);
bool (*peek_data)(struct mbox_chan *chan);
};
- send_data: API请求MBOX控制器驱动程序,在原子上下文中尝试在总线上传输消息。如果数据被接受进行传输,则返回0;如果遥控器尚未读取最后发送的数据,则返回-EBUSY,同时拒绝。控制器通过mbox_chan_txdone报告数据的实际传输(如果它具有一些TX ACK irq)。
- flush: 当客户端请求阻止传输但上下文不允许睡眠时调用。通常,控制器将实现繁忙循环,等待数据清空。
- startup: 当客户请求chan时调用。控制器可以请求客户端通过客户端的chan_data来提供通信的附加参数。此调用可能会被阻止。在该调用之后,控制器必须通过调用mbox_chan_received_data来转发在chan上接收到的任何数据。控制器可能会做一些需要睡眠的事情。
- shutdown: 当客户放弃对chan的控制时调用。此调用也可能被阻止。控制器不得再转发任何接收到的数据。控制器可能会做一些需要睡眠的事情。
- last_tx_done: 如果控制器设置了“txdone_poll”,API会调用它来轮询上一个TX的状态。控制器必须优先于IRQ方法而不是轮询,并且永远不要同时设置txdone_poll和txdone_IRQ。只有在轮询模式下,“send_data”才应返回-EBUSY。控制器可能会做一些需要睡眠/阻塞的事情。仅当txdone_poll:=true&&txdone_irq:=false时使用。
- peek_data: 对任何接收到的数据进行原子检查。如果控制器有一些数据要推送到客户端,则返回true。否则为False。
这几个结构体的关系如下:
- Mailbox Framework中有mbox_cons的list,mbox_controller注册的时候会添加到这个list中。
- mbox_controller中有chans,上面挂着需要mbox_controller管理的各个mbox_chan。这部分关系需要在device tree中声明。
- mbox_chan上挂着申请channel的mbox_clinet,每个mbox_chan有一个msg_data循环buffer,其中存放着mbox_chan发送的数据。
- 举个例子,在Qcom平台,soc跟AOP通信通过mailbox。其中mbox_controller为qmp_aop,msm_npu作为一个clinet注册一个channel,gpu_cx_gdsc作为一个clinet注册为一个channel。
DeviceTree
通用mbox_controller和mbox_client驱动程序绑定通过device tree。
下面还以msm_npu,gpu_cx_gdsc以及qmp_aop作为举例子:
msm_npu: qcom,msm_npu@9800000 {
compatible = "qcom,msm-npu";
mboxes = <&qmp_aop 0>;
mbox-names = "aop";
};
gpu_cx_gdsc: qcom,gdsc@2c9106c {
compatible = "qcom,gdsc";
regulator-name = "gpu_cx_gdsc";
reg = <0x2c9106c 0x4>;
hw-ctrl-addr = <&gpu_cx_hw_ctrl>;
qcom,skip-disable;
qcom,gds-timeout = <500>;
qcom,clk-dis-wait-val = <8>;
mboxes = <&qmp_aop 0>;
};
qmp_aop: qcom,qmp-aop@c300000 {
compatible = "qcom,qmp-mbox";
reg = <0xc300000 0x1000>, <0x17c0000C 0x4>;
reg-names = "msgram", "irq-reg-base";
qcom,irq-mask = <0x1>;
interrupts = <GIC_SPI 389 IRQ_TYPE_EDGE_RISING>;
label = "aop";
qcom,early-boot;
priority = <0>;
mbox-desc-offset = <0x0>;
#mbox-cells = <1>;
};
必要属性:
mboxes属性:指向当前节点作为channel要注册的controller,例如msm_npu和gdsc节点作为channel需要注册到qmp_aop的controller上。
mbox-cells属性:表明当前节点要注册成为controller。
可选属性:
mbox-names:注册的channel的名称。
注册
int mbox_controller_register(struct mbox_controller *mbox); /* can sleep */
- 用来注册mbox_controller,参数为要注册的mbox_controller。
- 注册所有挂在当前mbox_controller上channel的ops。
- 解析dt获取当前mbox_controller上的num_chans。
- 配置txdone方式,TXDONE_BY_IRQ,TXDONE_BY_POLL或者TXDONE_BY_ACK。
- mbox_controller添加到mbox_cons。
struct mbox_chan *mbox_request_channel(struct mbox_client *cl, int index);
- 用来为mbox_client申请mbox_chan,参数为要注册的mbox_client,和索引index。
- 在mbox_controller找到一个channel。
- 调用mbox_controller上的ops去startup channel。
工作原理
以txdone为IRQ模式举个例子
clinet会调用mbox_send_message发送msg,subsystem接收到msg的时候会通过IRQ确认,告知SOC收到msg。
上图蓝色线是发送流程,红色线是接收流程。
发送流程:
- mbox_send_message首先会通过调用add_to_rbuf将msg添加到channel的msg buffer中。
- msg_submit主要是调用controller的ops的send函数,将当前channel中未发送的msg buffer逐个发送。
- 最后clinet如果设置了tx_block,则等待当前channel上的msg的tx_compelete。
接收流程:
- subsystem接收到msg的时候会通过IRQ确认,SOC收到IRQ的时候会调用mbox_chan_txdone。
- mbox_chan_txdone主要调用tx_tick,tx_tick同样会调用msg_submit,继续逐个发送channel中未发送的msg buffer
- tx_tick调用clinet的tx_done,告知clinet,subsystem处理完当前的msg。
- tx_tick最后设置channel上的msg的tx_compelete。
当clinet的驱动调用mbox_send_message去发送msg的时候,msg会放在clinet对应chan的msg_data中。代码实现如下:
static int add_to_rbuf(struct mbox_chan *chan, void *mssg)
{
int idx;
unsigned long flags;
spin_lock_irqsave(&chan->lock, flags);
/* See if there is any space left */
if (chan->msg_count == MBOX_TX_QUEUE_LEN) {
spin_unlock_irqrestore(&chan->lock, flags);
return -ENOBUFS;
}
idx = chan->msg_free;
chan->msg_data[idx] = mssg;
chan->msg_count++;
if (idx == MBOX_TX_QUEUE_LEN - 1)
chan->msg_free = 0;
else
chan->msg_free++;
spin_unlock_irqrestore(&chan->lock, flags);
return idx;
}
- msg_data是用来循环存储msg的buffer。
- msg_free指向当前添加的buffer。
- msg_count是表示有当前有几个msg没有发送。
下面看看msg_submit函数,比较简单。
static void msg_submit(struct mbox_chan *chan)
{
int err = 0;
/*
* If the controller returns -EAGAIN, then it means, our spinlock
* here is preventing the controller from receiving its interrupt,
* that would help clear the controller channels that are currently
* blocked waiting on the interrupt response.
* Retry again.
*/
do {
err = __msg_submit(chan);
} while (err == -EAGAIN);
/* kick start the timer immediately to avoid delays */
if (!err && (chan->txdone_method & TXDONE_BY_POLL)) {
/* but only if not already active */
if (!hrtimer_active(&chan->mbox->poll_hrt))
hrtimer_start(&chan->mbox->poll_hrt, 0, HRTIMER_MODE_REL);
}
}
- 调用了__msg_submit函数,如果返回EAGAIN就循环发送。
- txdone的模式设置为TXDONE_BY_POLL的时候启动poll的timer。
static int __msg_submit(struct mbox_chan *chan)
{
unsigned count, idx;
unsigned long flags;
void *data;
int err = -EBUSY;
spin_lock_irqsave(&chan->lock, flags);
if (!chan->msg_count || chan->active_req)
goto exit;
count = chan->msg_count;
idx = chan->msg_free;
if (idx >= count)
idx -= count;
else
idx += MBOX_TX_QUEUE_LEN - count;
data = chan->msg_data[idx];
if (chan->cl->tx_prepare)
chan->cl->tx_prepare(chan->cl, data);
/* Try to submit a message to the MBOX controller */
err = chan->mbox->ops->send_data(chan, data);
if (!err) {
chan->active_req = data;
chan->msg_count--;
}
exit:
spin_unlock_irqrestore(&chan->lock, flags);
return err;
}
- 根据channel的msg_free和msg_count找到channel需要发送的msg。
- clinet调用tx_prepare。
- 最后调用controller ops中的send_data发送msg。
- 最后channel的active_req设置为当前的msg。
使用举例
下面以Qcom sa8155上mailbox的使用举例子。
我们可以看到qcom的sa8155主要注册了3个controller,分别为:
- c300000.qcom,qmp-aop
- 17c00000.mailbox
- 188501c.mailbox
实现在以下文件中,大家有兴趣可以研究。
kernel/msm-5.4/drivers/mailbox/msm_qmp.c
kernel/msm-5.4/drivers/mailbox/qcom-apcs-ipc-mailbox.c