tags : linux driver
最近研究高通平台的音频相关,由于实在受不了没头没尾的数据,所以就顺便研究了一下smd以及smem,这篇博客就是一个笔记,比较杂乱,主要用来备忘!
网上找到一些关于smd的文章,但是基本上都仅限于smd,只讲述了smd的channel相关,但是channel的内存到底哪里来的,smem到底是怎么回事好像都没提……专门写smem的文章里面好像对smem的描述也仅限于高通官方的一个txt,很多细节也没说清楚,尤其是channel与smem怎么对应上的,以及channel所使用smem到底哪里创建出来的,只字未提,所以就抽时间研究了一下,这里做下笔记,所以是想到哪写到哪的,可能也只有我自己看得懂……
smem里面的id就是一个item的编号,也是这个item的type,一个to_proc就是一个partition,to_proc就是partition的编号
关于smem怎么跟其他设备交互的,以audio相关的dsp为例,dsp在820平台上其设备号被定义为2
enum {
SMD_APPS = SMEM_APPS,
SMD_MODEM = SMEM_MODEM,
SMD_Q6 = SMEM_Q6,//这个就是处理音频的dsp
SMD_DSPS = SMEM_DSPS,
SMD_TZ = SMEM_DSPS,
SMD_WCNSS = SMEM_WCNSS,
SMD_MODEM_Q6_FW = SMEM_MODEM_Q6_FW,
SMD_RPM = SMEM_RPM,
NUM_SMD_SUBSYSTEMS,
};
对于smem的使用,根据高通的说明文档,有security和普通模式,这里使用的是security模式。
- smem的使用方式,应该跟双口ram是一个道理,两边都可以向同一个内存中写,写完后通过一个中断通知对端cpu来取数据,同时写的时候应该会有一个指定的帧格式,让对方知道这次写的是什么消息
- 每个partition用来指定一对处理器交互空间,也就是指定这一段内存是给哪两个处理器使用,由平台设计者事先约定好,通过某一个处理器在linux系统启动前已经把partition的toc写在了dts配置的smem对应的位置了,至于这个toc是谁写的?我觉得其实哪个处理器都可以来实现这个操作,cpu可以做,dsp可以做,modem也可以做,因为这仅仅是个共享内存块,只要大家约定好一个人来写就行。从其他地方的资料来看好像是cpu在bootloader里面做的这件事,但是这个究竟是不是,无法验证,但是原理上就是这样,谁来写都行!!
- 每个cup启动后,可以根据toc里面的规定,找到自己跟其他处理器通信到底该使用哪个partition,同样,每个cpu可以都可以向partition里面建立item,所以!!!不要天经地义的以为所有的item都该cpu建立,这里我之前就一直没转过弯来,总觉得所有partition都该cpu来创建,结果发现关于smd中channel使用的item SMEM_SMD_BASE_ID + ch_n,以及SMEM_CHANNEL_ALLOC_TBL等等编号的item,在linux中无论怎么跟踪,怎么加打印从任何地方都找不到其在哪里创建的,但是当smem的第一个中断到来,再去读dsp对应的partition时,发现该partition的header内容已经被修改了!!!在linux中完全跟踪不到!!所以,这里猜测,当dps启动后会去读toc相关信息,然后根据相关信息,创建其需要创建的item。这样的猜测我觉得是唯一能解释目前实验现象的,同时在系统设计上也是合理的!!比如说:如果channel的创建由外部处理器来创建,则cpu可以容易的知道当前平台上到底挂接了哪些可用的外部处理器。
- 现在通过实验又多了一个佐证,也就是在dsp第一个中断来之前,读partition2的SMEM_CHANNEL_ALLOC_TBL类型item时,始终读不到,一旦第一个中中断到来,则读到了,所以可以从侧面证明在linux中找不到的item都是被其他处理器创建的,只不过目前没有根本的证据能证明这一点。高通,你敢把dsp的代码开源么?!!
言归正传,来研究smd
smd应该是高通共享内存设备,对下,通过操作smem来实现共享内存的物理操作,对上提供channel进行操作。
sdm.c(drivers\soc\qcom)
arch_initcall(msm_smd_init);
msm_smd_init()->msm_smd_driver_register();完成设备驱动注册
msm_smd_init()函数中对全局数组remote_info[]进行了初始化,该初始化最主要的就是把remote_pid给赋值了,这个赋值是根据NUM_SMD_SUBSYSTEMS个枚举类型里面的值来赋值的,NUM_SMD_SUBSYSTEMS枚举类型里面的值又是NUM_SMEM_SUBSYSTEMS这个枚举类型定义的,NUM_SMEM_SUBSYSTEMS枚举类型就是smem设备的实际定义,在高通820平台开发板下,smen采用安全模式,所以会分成若干个partition,NUM_SMEM_SUBSYSTEMS枚举就跟这若干个partition是对应的,并且每个partition的host0和host1实在bootloader时就定义好了的,至于bootloader是从哪里定义的,暂时没有深究,这部分可以通过smem_init_security()(drivers\soc\qcom\smem.c)这个函数中的打印来查看,这里的打印会把smem_toc信息打印出来。
struct smem_toc结构对应的应该是sevurity模式下的toc,被记录在整个smem的最后面,有4k的空间。
struct smem_shared结构对应的应该是普通模式下TOC信息 在security模式下,Legacy/Default SMEM Partition 存储的应该是普通模式下的TOC信息。
关于smem的详细介绍可以参考高通的自说明文档msm_smem.txt,里面说明了smem的定义,非安全模式、安全模式、cache和uncache的栈生长方向等,顺便记录一下,文档中所描述的Item x Header信息,对应到代码里面应该是 smem_partition_allocation_header结构,smem_partition_header应该是每个partition的头,这里仅仅是猜测,但是从部分实验现象来看大概率应该是这样。
ps1:smd中edge的值应该是和partition的块序号是对应的,即edge 0则对应partition 0。
ps2:partition中,smem_alloc()为创建partition的具体执行函数,该函数在多个地方被调用,本平台中,创建modem和adsp的partition是在glink_smem_native_probe()(drivers\soc\qcom\glink_smem_native_xprt.c)中进行的,glink 这个东西到底做了什么这里有待进一步分析,同时有个glink pkt不知道跟这有没有关系……
这里,glink是作为SEC_ALLOC_TBL,而音频实际使用的tbl应该是PRI_ALLOC_TBL,两个tab的toc号,也就是type不同,pri的定义:base_id = SMEM_SMD_BASE_ID;fifo_id = SMEM_SMD_FIFO_BASE_ID;,SEC_ALLOC_TBL的定义:base_id = SMEM_SMD_BASE_ID_2;fifo_id = SMEM_SMD_FIFO_BASE_ID_2;
上述这些主要是记录一下pid是哪里来的,怎么来的。
下面分析smd初始化的核心,注册驱动,注册的驱动是如何被调用到probe的就不记录了,那是linux驱动模型里面该掌握的。
int msm_smd_driver_register(void)
{
int rc;
rc = platform_driver_register(&msm_smd_driver);
if (rc) {
pr_err("%s: smd_driver register failed %d\n",
__func__, rc);
return rc;
}
rc = platform_driver_register(&msm_smsm_driver);
if (rc) {
pr_err("%s: msm_smsm_driver register failed %d\n",
__func__, rc);
return rc;
}
return 0;
}
这个函数里面其实就是注册了smd驱动以及smsm驱动,结果发现我的开发板上面好像是没有smsm设备的,所以这里是做什么的也不知道……暂时只管smd驱动。
static int msm_smd_probe(struct platform_device *pdev)
{
uint32_t edge;
char *key;
int ret;
uint32_t irq_offset;
uint32_t irq_bitmask;
uint32_t irq_line;
const char *subsys_name;
struct interrupt_config_item *private_irq;
struct device_node *node;
void *irq_out_base;
resource_size_t irq_out_size;
struct platform_device *parent_pdev;
struct resource *r;
struct interrupt_config *private_intr_config;
uint32_t remote_pid;
bool skip_pil;
node = pdev->dev.of_node;
if (!pdev->dev.parent) {
pr_err("%s: missing link to parent device\n", __func__);
return -ENODEV;
}
mutex_lock(&smd_probe_lock);
if (!first_probe_done) {
smd_reset_all_edge_subsys_name();//把bus名称清空
first_probe_done = 1;
}
mutex_unlock(&smd_probe_lock);
parent_pdev = to_platform_device(pdev->dev.parent);
//从这里开始,到后面一段都是在读取硬件资源,比如中断呀,edge值呀等等,
//硬件资源都是dts中定义的,dts的加载、device的复原等等,见另一篇笔记
key = "irq-reg-base";
r = platform_get_resource_byname(parent_pdev, IORESOURCE_MEM, key);
if (!r)
goto missing_key;
irq_out_size = resource_size(r);
irq_out_base = ioremap_nocache(r->start, irq_out_size);
if (!irq_out_base) {
pr_err("%s: ioremap_nocache() of irq_out_base addr:%pr size:%pr\n",
__func__, &r->start, &irq_out_size);
return -ENOMEM;
}
SMD_DBG("%s: %s = %p", __func__, key, irq_out_base);
key = "qcom,smd-edge";
ret = of_property_read_u32(node, key, &edge);
if (ret)
goto missing_key;
SMD_DBG("%s: %s = %d", __func__, key, edge);
key = "qcom,smd-irq-offset";
ret = of_property_read_u32(node, key, &irq_offset);
if (ret)
goto missing_key;
SMD_DBG("%s: %s = %x", __func__, key, irq_offset);
key = "qcom,smd-irq-bitmask";
ret = of_property_read_u32(node, key, &irq_bitmask);
if (ret)
goto missing_key;
SMD_DBG("%s: %s = %x", __func__, key, irq_bitmask);
key = "interrupts";
irq_line = irq_of_parse_and_map(node, 0);
if (!irq_line)
goto missing_key;
SMD_DBG("%s: %s = %d", __func__, key, irq_line);
key = "label";
subsys_name = of_get_property(node, key, NULL);
SMD_DBG("%s: %s = %s", __func__, key, subsys_name);
/*
* Backwards compatibility. Although label is required, some DTs may
* still list the legacy pil-string. Sanely handle pil-string.
*/
if (!subsys_name) {
pr_warn("msm_smd: Missing required property - label. Using legacy parsing\n");
key = "qcom,pil-string";
subsys_name = of_get_property(node, key, NULL);
SMD_DBG("%s: %s = %s", __func__, key, subsys_name);
if (subsys_name)
skip_pil = false;
else
skip_pil = true;
} else {
key = "qcom,not-loadable";
skip_pil = of_property_read_bool(node, key);
SMD_DBG("%s: %s = %d\n", __func__, key, skip_pil);
}
/* 差不多一直到这里,才把硬件资源读完 */
/* 这里是找到中断服务函数,这个服务函数的映射关系也是根据smem那边定义好的对应关系,
是一个根据事先约定写死了的数组,当其他处理器给smem写了东西之后,给本地ap的中断,
然后调用该函数,告诉使用该channel的应用程序smem中有数据可以用了,
中断函数里面ch->notify()就是创建通道时使用通道者注册进来的函数。*/
private_intr_config = smd_get_intr_config(edge);
if (!private_intr_config) {
pr_err("%s: invalid edge\n", __func__);
return -ENODEV;
}
private_irq = &private_intr_config->smd;
private_irq->out_bit_pos = irq_bitmask;
private_irq->out_offset = irq_offset;
private_irq->out_base = irq_out_base;
private_irq->irq_id = irq_line;/*这个东西怎么来的,过程太复杂了,
毕竟中断定义已经在dts中描述了,这里主要是做一些变换*/
remote_pid = smd_edge_to_remote_pid(edge);
interrupt_stats[remote_pid].smd_interrupt_id = irq_line;
/*看这个架势,这里估计是向系统注册一个中断……
linux中断的注册这块还没研究,等这些搞完了再来研究一下*/
ret = request_irq(irq_line,
private_irq->irq_handler,
/*这个东西是在private_intr_config = smd_get_intr_config(edge)
里面获取的,这里由于private_irq = &private_intr_config->smd;的存在,
所以private_irq就是private_intr_config里面的smd设备。*/
IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND | IRQF_SHARED,
node->name,
&pdev->dev);
if (ret < 0) {
pr_err("%s: request_irq() failed on %d\n", __func__, irq_line);
return ret;
} else {
//这里是不是就是使能中断的意思?
ret = enable_irq_wake(irq_line);
if (ret < 0)
pr_err("%s: enable_irq_wake() failed on %d\n", __func__,
irq_line);
}
//重新写bus的名称,开头一进来就给清空了的
smd_set_edge_subsys_name(edge, subsys_name);
//暂时不清楚意义
smd_proc_set_skip_pil(smd_edge_to_remote_pid(edge), skip_pil);
//设置已经初始化了的标识,后面使用smd时会先判断edge初始化标识的
smd_set_edge_initialized(edge);
//创建channel,下面具体展开分析。
smd_post_init(remote_pid);
return 0;
missing_key:
pr_err("%s: missing key: %s", __func__, key);
return -ENODEV;
}
void smd_post_init(unsigned remote_pid)
{
smd_channel_probe_now(&remote_info[remote_pid]);
}
/*smd_channel_probe_now()这个函数就是在初始化和smem中断中被调用,
在初始化时被调用了没什么用,因为dsp这个时候还没起来,smem_get_entry()
直接啥都get不到。(这里面要get的item猜测极大概率是被dsp创建的,
所以cpu初始化时是找不到他所要的item的)
所以,这个函数主要是在smem中断中被调用,中断回调函数调用static void
do_smd_probe(unsigned remote_pid)函数,
该函数完成schedule_work()的触发,而在初始化时,smd_channel_probe_worker()
函数被装入了work队列,smd_channel_probe_worker()又是smd_channel_probe_now()
的最终调用者,所以当中断smem中断到来,通过work机制最终调用此函数。
*/
static void smd_channel_probe_now(struct remote_proc_info *r_info)
{
struct smd_alloc_elm *shared;
unsigned tbl_size;
shared = smem_get_entry(ID_CH_ALLOC_TBL, &tbl_size,
r_info->remote_pid, 0);
if (!shared) {
pr_err("%s: allocation table not initialized\n", __func__);
return;
}
mutex_lock(&smd_probe_lock);
//扫描pri tbl的通道是否全部已经创建,若没有创建,则创建所有channel
scan_alloc_table(shared, r_info->ch_allocated, PRI_ALLOC_TBL,
tbl_size / sizeof(*shared),
r_info);
shared = smem_get_entry(SMEM_CHANNEL_ALLOC_TBL_2, &tbl_size,
r_info->remote_pid, 0);
if (shared)
//扫描sec tbl的通道是否全部已经创建,若没有创建,则创建所有channel,
//但是在820平台上实际情况是这里根本没有执行,应为相应的item没有被创建
scan_alloc_table(shared,
&(r_info->ch_allocated[SMEM_NUM_SMD_STREAM_CHANNELS]),
SEC_ALLOC_TBL,
tbl_size / sizeof(*shared),
r_info);
mutex_unlock(&smd_probe_lock);
}
static void scan_alloc_table(struct smd_alloc_elm *shared,
char *smd_ch_allocated,
int table_id,
unsigned num_entries,
struct remote_proc_info *r_info)
{
unsigned n;
uint32_t type;
for (n = 0; n < num_entries; n++) {
if (smd_ch_allocated[n])
continue;
/*
* channel should be allocated only if APPS processor is
* involved
*/
//这个type其实就是partition的编号
type = SMD_CHANNEL_TYPE(shared[n].type);
if (!pid_is_on_edge(type, SMD_APPS) ||
!pid_is_on_edge(type, r_info->remote_pid))
continue;
if (!shared[n].ref_count)
continue;
if (!shared[n].name[0])
continue;
if (!smd_edge_inited(type)) {
SMD_INFO(
"Probe skipping proc %d, tbl %d, ch %d, edge not inited\n",
r_info->remote_pid, table_id, n);
continue;
}
//实际创建通道函数
if (!smd_alloc_channel(&shared[n], table_id, r_info))
smd_ch_allocated[n] = 1;
else
SMD_INFO(
"Probe skipping proc %d, tbl %d, ch %d, not allocated\n",
r_info->remote_pid, table_id, n);
}
}
static int smd_alloc_channel(struct smd_alloc_elm *alloc_elm, int table_id,
struct remote_proc_info *r_info)
{
struct smd_channel *ch;
printk("gift_smem smd_alloc_channel table_id = %d \r\n", table_id);
ch = kzalloc(sizeof(struct smd_channel), GFP_KERNEL);
if (ch == 0) {
pr_err("smd_alloc_channel() out of memory\n");
return -ENOMEM;
}
ch->n = alloc_elm->cid;
ch->type = SMD_CHANNEL_TYPE(alloc_elm->type);
if (smd_alloc(ch, table_id, r_info)) {
kfree(ch);
return -ENODEV;
}
/* probe_worker guarentees ch->type will be a valid type */
//这里的函数都是通知对端处理器的方法,也就是本地cpu写完了ram之后触发一下,
//就可以通知对端cpu了
if (ch->type == SMD_APPS_MODEM)
ch->notify_other_cpu = notify_modem_smd;
else if (ch->type == SMD_APPS_QDSP)
ch->notify_other_cpu = notify_dsp_smd;
else if (ch->type == SMD_APPS_DSPS)
ch->notify_other_cpu = notify_dsps_smd;
else if (ch->type == SMD_APPS_WCNSS)
ch->notify_other_cpu = notify_wcnss_smd;
else if (ch->type == SMD_APPS_Q6FW)
ch->notify_other_cpu = notify_modemfw_smd;
else if (ch->type == SMD_APPS_RPM)
ch->notify_other_cpu = notify_rpm_smd;
if (smd_is_packet(alloc_elm)) {
ch->read = smd_packet_read;
ch->write = smd_packet_write;
ch->read_avail = smd_packet_read_avail;
ch->write_avail = smd_packet_write_avail;
ch->update_state = update_packet_state;
ch->read_from_cb = smd_packet_read_from_cb;
ch->is_pkt_ch = 1;
} else {
ch->read = smd_stream_read;
ch->write = smd_stream_write;
ch->read_avail = smd_stream_read_avail;
ch->write_avail = smd_stream_write_avail;
ch->update_state = update_stream_state;
ch->read_from_cb = smd_stream_read;
}
if (is_word_access_ch(ch->type)) {
ch->read_from_fifo = smd_memcpy32_from_fifo;
ch->write_to_fifo = smd_memcpy32_to_fifo;
} else {
ch->read_from_fifo = smd_memcpy_from_fifo;
ch->write_to_fifo = smd_memcpy_to_fifo;
}
smd_memcpy_from_fifo(ch->name, alloc_elm->name, SMD_MAX_CH_NAME_LEN);
ch->name[SMD_MAX_CH_NAME_LEN-1] = 0;
ch->pdev.name = ch->name;
ch->pdev.id = ch->type;
SMD_INFO("smd_alloc_channel() '%s' cid=%d\n",
ch->name, ch->n);
/*把新创建的channel放入smd_ch_closed_list全局链表中,
这里面应该是保存所未被使用的ch的,从smd_get_channel()函数中可以佐证这一点,
还有两个跟他很像的全局链表是干嘛后面碰到了再研究*/
mutex_lock(&smd_creation_mutex);
list_add(&ch->ch_list, &smd_ch_closed_list);
mutex_unlock(&smd_creation_mutex);
//把这个ch注册成一个设备,那么肯定会有ch对应的驱动的,后面再找
/*这里找到了,比如说audio,对应的ch驱动就是"apr_audio_svc",
其他的驱动也都是在别的文件里面有对应的,把ch->pdev.name打印出来,
然后搜一下就可以找到了,因为ch->pdev.name和driver的name一定是匹配的
(linux驱动模型通过名字匹配设备和驱动)*/
platform_device_register(&ch->pdev);
if (!strncmp(ch->name, "LOOPBACK", 8) && ch->type == SMD_APPS_MODEM) {
/* create a platform driver to be used by smd_tty driver
* so that it can access the loopback port
*/
loopback_tty_pdev.id = ch->type;
platform_device_register(&loopback_tty_pdev);
}
return 0;
}
static int smd_alloc(struct smd_channel *ch, int table_id,
struct remote_proc_info *r_info)
{
void *buffer;
unsigned buffer_sz;
unsigned base_id;
unsigned fifo_id;
printk("gift_smem %s ch=%d\n", __func__, ch->n);
switch (table_id) {
case PRI_ALLOC_TBL:
base_id = SMEM_SMD_BASE_ID;
fifo_id = SMEM_SMD_FIFO_BASE_ID;
break;
case SEC_ALLOC_TBL:
base_id = SMEM_SMD_BASE_ID_2;
fifo_id = SMEM_SMD_FIFO_BASE_ID_2;
break;
default:
SMD_INFO("Invalid table_id:%d passed to smd_alloc\n", table_id);
return -EINVAL;
}
if (is_word_access_ch(ch->type)) {
struct smd_shared_word_access *shared2;
//根据item的id和partition的id来查找其对应的内存入口地址
shared2 = smem_find(base_id + ch->n, sizeof(*shared2),
r_info->remote_pid, 0);
if (!shared2) {
printk("gift_smem word find failed ch=%d\n", ch->n);
SMD_INFO("smem_find failed ch=%d\n", ch->n);
return -EINVAL;
}
//如果找到了就把接收缓冲区和发送缓冲区的内存地址保存下来
//这里应该只是channel的信息,并不是实际的数据内存,
//可能只是传输一些此次对fifo操作的控制信息
ch->send = &shared2->ch0;
ch->recv = &shared2->ch1;
} else {
struct smd_shared *shared2;
shared2 = smem_find(base_id + ch->n, sizeof(*shared2),
r_info->remote_pid, 0);
if (!shared2) {
printk("gift_smem find failed ch=%d\n", ch->n);
SMD_INFO("smem_find failed ch=%d\n", ch->n);
return -EINVAL;
}
ch->send = &shared2->ch0;
ch->recv = &shared2->ch1;
}
/*这里是把通道的实际操作方法保存到half_ch里面,其实这里感觉更像是一个内存映射,
到时候需要从内存中取什么数据的时候,估计就是用类似ch->half_ch->xxx(ch->send);
的方式。具体这个结构体static struct smd_half_channel_access这样定义的原因,
应该就是各cpu之间的通信格式约定了。至于这个通信格式具体定义,高通也没说呀,
要我去哪里找?只能猜么?*/
ch->half_ch = get_half_ch_funcs(ch->type);
//这里就是取实际的数据内存地址,估计就是用来保存需要交互的实际数据的
buffer = smem_get_entry(fifo_id + ch->n, &buffer_sz,
r_info->remote_pid, 0);
if (!buffer) {
SMD_INFO("smem_get_entry failed\n");
return -EINVAL;
}
/* buffer must be a multiple of 32 size */
if ((buffer_sz & (SZ_32 - 1)) != 0) {
SMD_INFO("Buffer size: %u not multiple of 32\n", buffer_sz);
return -EINVAL;
}
buffer_sz /= 2;
ch->send_data = buffer;
/*这一句话写的很风骚啊,他确信内存的连续性,以及协议的正确性,所以直接这样操作,
如果ch->send_data后面那一坨内存可以被其他地方先占用,那这里就不行了……*/
ch->recv_data = buffer + buffer_sz;
ch->fifo_size = buffer_sz;
return 0;
}