linux mailbox(The Common Mailbox Framework)

概述

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。

这几个结构体的关系如下:
在这里插入图片描述

  1. Mailbox Framework中有mbox_cons的list,mbox_controller注册的时候会添加到这个list中。
  2. mbox_controller中有chans,上面挂着需要mbox_controller管理的各个mbox_chan。这部分关系需要在device tree中声明。
  3. mbox_chan上挂着申请channel的mbox_clinet,每个mbox_chan有一个msg_data循环buffer,其中存放着mbox_chan发送的数据。
  4. 举个例子,在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。
上图蓝色线是发送流程,红色线是接收流程。
发送流程

  1. mbox_send_message首先会通过调用add_to_rbuf将msg添加到channel的msg buffer中。
  2. msg_submit主要是调用controller的ops的send函数,将当前channel中未发送的msg buffer逐个发送。
  3. 最后clinet如果设置了tx_block,则等待当前channel上的msg的tx_compelete。

接收流程:

  1. subsystem接收到msg的时候会通过IRQ确认,SOC收到IRQ的时候会调用mbox_chan_txdone。
  2. mbox_chan_txdone主要调用tx_tick,tx_tick同样会调用msg_submit,继续逐个发送channel中未发送的msg buffer
  3. tx_tick调用clinet的tx_done,告知clinet,subsystem处理完当前的msg。
  4. 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);
	}
}
  1. 调用了__msg_submit函数,如果返回EAGAIN就循环发送。
  2. 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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值