在《Android SMD数据通信概述》一文中,我们了解到基于SMD的数据通信是以信道的形式作为一个设备存在的,作为一种双向信道,其接口的实现遵循Linux设备驱动规范。本文会结合SMD信道的实现简要介绍SMD信道的分配、打开、读取、写入、关闭等操作。
1.分配信道
SMD信道根据数据的类型可以分为流信道和包信道,其中包信道具有比流信道更强的流控制能力,包含头信息。在创建SMD信道时,会根据信道类型的不同,确定创建的是FIFO信道还是普通信道,是流信道还是包信道,然后为SMD进行设备注册。SMD分配信道的实现如下:
代码1-1 SMD分配信道的过程
static void smd_alloc_channel(struct smd_alloc_elm *alloc_elm)
{
struct smd_channel *ch;
uint32_t *smd_ver;
//分配SMEM内存
smd_ver=smem_alloc(SMEM_VERSION_SMD, 32 * sizeof(uint32_t));
if (smd_ver && ((smd_ver[VERSION_MODEM]>>16)>=1))
ch=smd_alloc_channel_v2(alloc_elm->cid); //FIFO信道
else
ch=smd_alloc_channel_v1(alloc_elm->cid); //普通信道
if (ch==0)
return;
ch->type=SMD_CHANNEL_TYPE(alloc_elm->type);
memcpy(ch->name, alloc_elm->name, 20);
ch->name[19]=0;
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;
} 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;
}
ch->pdev.name=ch->name;
ch->pdev.id=ch->type;
pr_info("smd_alloc_channel() '%s' cid=%d\n",ch->name, ch->n);
mutex_lock(&smd_creation_mutex); //互斥锁
//将信道添加到“smd_ch_closed_list”列表中
list_add(&ch->ch_list, &smd_ch_closed_list); mutex_unlock(&smd_creation_mutex);
platform_device_register(&ch->pdev); //注册设备
}
2.打开信道
为了打开一个信道,首先要判断SMD信道是否已经初始化。如果SMD信道已经初始化,就根据信道名获得信道,将信道加入到“smd_ch_list”信道列表中并设置该信道的状态为SMD_SS_OPENING,然后调用notify_other_smd()函数通知其他的信道该信道已经激活。在默认情况下,其信道类型为SMD_APPS_MODEM,打开一个SMD信道的实现如下:
代码1-2 SMD打开信道的过程
int smd_named_open_on_edge(const char *name, uint32_t edge,smd_channel_t **_ch,void *priv, void (*notify)(void *, unsigned))
{
struct smd_channel *ch;
unsigned long flags;
if (smd_initialized==0) { //判断SMD信道是否已初始化
printk(KERN_INFO "smd_open() before smd_init()\n");
return -ENODEV;
}
D("smd_open('%s', %p, %p)\n", name, priv, notify);
ch=smd_get_channel(name, edge); //获取信道
if (!ch)
return -ENODEV;
if (notify==0)
notify=do_nothing_notify;
ch->notify=notify;
ch->current_packet=0;
ch->last_state=SMD_SS_CLOSED;
ch->priv=priv;
*_ch=ch;
D("smd_open: opening '%s'\n", ch->name);
spin_lock_irqsave(&smd_lock, flags); //自旋锁
list_add(&ch->ch_list, &smd_ch_list); //将信道添加到“smd_ch_list”列表中
D("%s: opening ch %d\n", __func__, ch->n);
smd_state_change(ch, ch->last_state, SMD_SS_OPENING); //信道状态变更
spin_unlock_irqrestore(&smd_lock, flags);
return 0;
}
3.关闭信道
关闭信道的操作相对简单,首先将信道从“smd_ch_list”信道列表中删除,然后将信道状态设置为SMD_SS_CLOSED,并将信道添加到“smd_ch_closed_list”信道列表中即可。关闭SMD信道的实现如下:
代码1-3 SMD关闭信道的过程
int smd_close(smd_channel_t *ch)
{
unsigned long flags;
printk(KERN_INFO "smd_close(%p)\n", ch);
if (ch==0)
return -1;
spin_lock_irqsave(&smd_lock, flags); //自旋锁
ch->notify=do_nothing_notify;
list_del(&ch->ch_list); //从打开信道列表中去除该信道
ch_set_state(ch, SMD_SS_CLOSED); //设置信道状态
spin_unlock_irqrestore(&smd_lock, flags);
mutex_lock(&smd_creation_mutex); //互斥锁
list_add(&ch->ch_list, &smd_ch_closed_list); //将信道添加至关闭信道列表
mutex_unlock(&smd_creation_mutex);
return 0;
}
4.信道读取
包信道的内容读取涉及缓冲的复制、与其他SMD的消息通信和包状态的更新。从包信道读取数据的实现如下:
代码1-4 SMD包信道读取数据的过程
static int smd_packet_read(smd_channel_t *ch, void *data, int len)
{
unsigned long flags;
int r;
if (len< 0)
return -EINVAL;
if (len>ch->current_packet)
len=ch->current_packet;
r=ch_read(ch, data, len); //读取数据
if (r>0)
notify_other_smd(ch->type);
spin_lock_irqsave(&smd_lock, flags); //自旋锁
ch->current_packet-=r;
update_packet_state(ch); //更新包状态
spin_unlock_irqrestore(&smd_lock, flags);
return r;
}
流信道的内容读取非常简单,只需要调用ch_read()函数读取数据并通知其他SMD该信道处于打开状态即可。流信道读取的实现如下:
代码1-5 SMD流信道读取数据的过程
static int smd_stream_read(smd_channel_t *ch, void *data, int len)
{
int r;
if (len< 0)
return -EINVAL;
r=ch_read(ch, data, len); //读取数据
if (r>0)
notify_other_smd(ch->type); //通知其他SMD,该信道处于激活状态。
return r;
}
流信道和包信道在读取信道数据时,都需要调用ch_read()函数来实现真正的数据读取,ch_read()函数的实现如下:
代码1-6 SMD信道读取数据的过程
static int ch_read(struct smd_channel *ch, void *_data, int len)
{
void *ptr;
unsigned n;
unsigned char *data=data;
int orig_len=len;
while (len>0) {
n=ch_read_buffer(ch, &ptr); //读取缓冲
if (n==0)
break;
if (n>len)
n=len;
if (data)
memcpy(data, ptr, n); //数据复制
data+=n;
len-=n;
ch_read_done(ch,n); //读取完成
}
return orig_len-len;
}
5.信道写入
在信道的数据写入方面,同样分为流信道数据的写入和包信道数据的写入两种类型。
向包信道中写入数据的过程和读取数据不同,在写入数据前,要首先利用smd_stream_write_avail()函数判断是否有数据可供写入,在确定有可供写入的数据的情况下才调用smd_stream_write()函数执行数据的写入操作。包信道写入数据的实现如下:
代码1-7 SMD包信道写入数据的过程
static int smd_packet_write(smd_channel_t *ch, const void *_data, int len)
{
int ret;
unsigned hdr[5];
D("smd_packet_write() %d->ch%d\n",len, ch->n);
if (len< 0)
return-EINVAL;
if (smd_stream_write_avail(ch)<(len+SMD_HEADER_SIZE)) //判断数据写入进度
return-ENOMEM;
hdr[0]=len;
hdr[1]=hdr[2]=hdr[3]=hdr[4]=0;
ret=smd_stream_write(ch, hdr, sizeof(hdr)); //流写入
if (ret< 0 || ret!=sizeof(hdr)) {
D("%s failed to write pkt header: ""%d returned\n", __func__, ret);
return -1;
}
ret=smd_stream_write(ch, _data, len);
if (ret< 0 || ret != len) {
D("%s failed to write pkt data: ""%d returned\n", __func__, ret);
return ret;
}
return len;
}
流信道数据的写入和包信道数据的写入不同,其首先获得下一段可用缓冲的指针,然后执行内存复制,流信道写入数据的实现如下:
代码1-8 SMD流信道写入数据的过程
static int smd_stream_write(smd_channel_t *ch, const void *_data, int len)
{
void *ptr;
const unsigned char *buf=data;
unsigned xfer;
int orig_len=len;
D("smd_stream_write() %d->ch%d\n", len, ch->n);
if (len< 0)
return -EINVAL;
while ((xfer=ch_write_buffer(ch, &ptr)) != 0) //写入数据
{
if (!ch_is_open(ch))
break;
if (xfer>len)
xfer=len;
memcpy(ptr, buf, xfer); //内存复制
ch_write_done(ch, xfer); //完成写入数据
len-=xfer;
buf+=xfer;
if (len==0)
break;
}
if (orig_len-len)
notify_other_smd(ch->type); //通知其他SMD,该信道处于激活状态
return orig_len-len;
}
SMD是多核通信的基础,在SMD之上是一个叫做RPC路由器(RPC Router)的封装。关于RPC路由器,将在3.2节过程调用中详细介绍。
通过SMD,可以为系统提供RPC、DIAG、AT命令、NMEA(GPS数据)、数据服务、拨号等服务。