Q1. 多个si3050 模块,共用PCM 数据线,数据是怎么组装的?
在PCM 中各通道的数据组装后格式如下:
chn1 | chn2 | chn3 | chn4 | … | chn8 |
---|
那模块是怎么知道自己数据要放到PCM的那个位置的呢?
找到相应代码如下:
数据手册相应的寄存器作用如下:
该寄存器设置了从FSYNC 信号后的第几个PCLK开始传输/接收数据,由此它知道了它的数据是要在PCM 中的哪个位置。
Q2. DAHDI 驱动中的PCM数据是如何与asterisk进行交换的?
也就是,asterisk 中是如何读写 DAHDI的PCM数据的?
asterisk 代码
load_module
res = setup_dahdi(0);
res = setup_dahdi_int(reload, &default_conf, &base_conf, &conf);
process_dahdi(...)
build_channels(confp, v->value, reload, v->lineno)
tmp = mkintf(x, conf, reload);
snprintf(fn, sizeof(fn), "%d", channel);
/* Open non-blocking */
tmp->subs[SUB_REAL].dfd = dahdi_open(fn); // open
dahdi_write
my_dahdi_write
fd = p->subs[idx].dfd;
res = write(fd, buf, size); // write
dahdi_read
res = read(p->subs[idx].dfd, readbuf, p->subs[idx].linear ? READ_SIZE * 2 : READ_SIZE);
// read
static int dahdi_open(char *fn)
{
int fd;
int isnum;
int chan = 0;
int bs;
int x;
isnum = 1;
for (x = 0; x < strlen(fn); x++) {
if (!isdigit(fn[x])) {
isnum = 0;
break;
}
}
if (isnum) {
chan = atoi(fn);
if (chan < 1) {
ast_log(LOG_WARNING, "Invalid channel number '%s'\n", fn);
return -1;
}
fn = "/dev/dahdi/channel";
}
fd = open(fn, O_RDWR | O_NONBLOCK);
if (fd < 0) {
ast_log(LOG_WARNING, "Unable to open '%s': %s\n", fn, strerror(errno));
return -1;
}
if (chan) {
if (ioctl(fd, DAHDI_SPECIFY, &chan)) {
x = errno;
close(fd);
errno = x;
ast_log(LOG_WARNING, "Unable to specify channel %d: %s\n", chan, strerror(errno));
return -1;
}
}
bs = READ_SIZE;
if (ioctl(fd, DAHDI_SET_BLOCKSIZE, &bs) == -1) {
ast_log(LOG_WARNING, "Unable to set blocksize '%d': %s\n", bs, strerror(errno));
x = errno;
close(fd);
errno = x;
return -1;
}
return fd;
}
从代码中看出是通过open “/dev/dahdi/channel” 文件,然后
- ioctl(fd, DAHDI_SPECIFY, &chan) 指定打开哪个通道,这里DAHDI_SPECIFY很关键
- ioctl(fd, DAHDI_SET_BLOCKSIZE, &bs) 设置Block 大小 160B ,即20ms的采样(8K采样率,8B /ms)
dahdi 代码
static int dahdi_open(struct inode *inode, struct file *file) //open
if (unit == DAHDI_CHANNEL)
return dahdi_chan_open(file); // dahdi_chan_open 什么也不做,直接返回0
dahdi_ioctl //ioctl
dahdi_unlocked_ioctl
if (unit == DAHDI_CHANNEL) {
if (file->private_data)
ret = dahdi_chan_ioctl(file, cmd, data); //DAHDI_SPECIFY 之后从这进入
else
ret = dahdi_prechan_ioctl(file, cmd, data); //DAHDI_SPECIFY 时调用,很关键
goto exit;
}
static int dahdi_prechan_ioctl(struct file *file, unsigned int cmd, unsigned long data)
{
int channo;
int res;
if (file->private_data) {
module_printk(KERN_NOTICE, "Huh? Prechan already has private data??\n");
}
switch(cmd) {
case DAHDI_SPECIFY: // 只响应 DAHDI_SPECIFY
get_user(channo, (int __user *)data);
file->private_data = chan_from_num(channo);
if (!file->private_data)
return -EINVAL;
res = dahdi_specchan_open(file);
if (res)
file->private_data = NULL;
return res;
default:
return -ENOSYS;
}
return 0;
}
static int dahdi_specchan_open(struct file *file)
const struct dahdi_span_ops *const ops =
(!is_pseudo_chan(chan)) ? chan->span->ops : NULL;
file->f_op = &dahdi_chan_fops; /*重定向f_op ,dahdi_chan_fops 重新定义了 open read write
ioctl 等函数 下次开始read write 这些系统调用实际会使用dahdi_chan_fops里的函数 */
if (ops && ops->open) {
res = ops->open(chan); // 这里调用 chan->span->ops-open 函数,即
/*dahdi_register_device 调用之前会指定chan->span->ops-open 函数,参见wctdm.c 的
wctdm_span_ops*/
dahdi_chan_fops 定义如下
static const struct file_operations dahdi_chan_fops = {
.owner = THIS_MODULE,
.open = dahdi_open,
.release = dahdi_release,
#ifdef HAVE_UNLOCKED_IOCTL
.unlocked_ioctl = dahdi_unlocked_ioctl,
#ifdef HAVE_COMPAT_IOCTL
.compat_ioctl = dahdi_ioctl_compat,
#endif
#else
.ioctl = dahdi_ioctl,
#endif
.read = dahdi_chan_read,
.write = dahdi_chan_write,
.poll = dahdi_chan_poll,
};
先看 dahdi_chan_write
dahdi_chan_write
res = chan->inwritebuf;
copy_from_user(chan->writebuf[res], usrbuf, amnt); // 把数据写入了chan->writebuf[res]
chan->writen[res] = amnt;
chan->writeidx[res] = 0;
oldbuf = res;
chan->inwritebuf = (res + 1) % chan->numbufs;
if (chan->inwritebuf == chan->outwritebuf) {
/* Don't stomp on the transmitter, just wait for them to
wake us up */
chan->inwritebuf = -1;
/* Make sure the transmitter is transmitting in case of POLICY_WHEN_FULL */
chan->txdisable = 0;
}
if (chan->outwritebuf < 0) {
/* Okay, the interrupt handler has been waiting for us. Give them a buffer */
chan->outwritebuf = oldbuf;
}
到现在可以看出,asterisk 里 write 的数据最终会写入到 chan->writebuf[res] 中,那么肯定会有地方从chan->writebuf[res] 中取出数据,在sourceinsight 中搜索一下:
搜到一个地方:__dahdi_getbuf_chunk 函数,提取关键代码
来看一下:
static inline void __dahdi_getbuf_chunk(struct dahdi_chan *ss, unsigned char *txb)
int bytes = DAHDI_CHUNKSIZE; // 8
while(bytes)
{
if ((ms->outwritebuf > -1) && !ms->txdisable)
{
buf= ms->writebuf[ms->outwritebuf];
left = ms->writen[ms->outwritebuf] - ms->writeidx[ms->outwritebuf];
if (left > bytes)
left = bytes;
memcpy(txb, buf + ms->writeidx[ms->outwritebuf], left);
ms->writeidx[ms->outwritebuf]+=left;
txb += left;
bytes -= left;
}
....
}
__dahdi_getbuf_chunk 函数主要作用是,从ms->writebuf[ms->outwritebuf] 中读取8个字节的数据
来看一下 __dahdi_getbuf_chunk 的调用过程
static inline int dahdi_transmit(struct dahdi_span *span)
ret = _dahdi_transmit(span);
__dahdi_real_transmit(chan);
__dahdi_transmit_chunk(chan, chan->writechunk);
__dahdi_transmit_chunk(chan, chan->writechunk); // 数据最终写到chan->writechunk中
__dahdi_getbuf_chunk(chan, buf);
dahdi_transmit 由驱动子模块的中断处理函数调用,参见wctdm.c
chan->writechunk 是指向 chan->swritechunk 的指针:
chan->swritechunk 是一个大小为8字节的数组。
到此千辛万苦把数据写入到了 chan->writechunk ,但是还没完,数据的最终目的是要传给si3050模块。也就是一定有地方把chan->writechunk 中的数据取出。
类似伪代码如下
for(i=0;i<8;i++)
{
for(chn = 0;chn < 8;chn++)
{
buf[chn*8+i] = wc->chans[chn]->writechunk[i]
}
}
// 然后将buf 写入pcm 总线。
总结:asterisk 写pcm 数据到 dahdi 驱动过程
- fd = dahdi_open(“n”) //n为通道号
- write(fd,buf ,buflen) ,最终将buf 写入chan->writebuf[res]
- 子模块驱动的中断响应函数调用 dahdi_transmit ,会将chan->writebuf[res] 中的数据读到 chan->writechunk
- 最后子模块驱动将chan->writechunk 的数据取出组装成自定义PCM格式,写入PCM总线
- PCM 总线的另一端,如si3050,根据预先设定的偏移值,取出PCM数据中的指定位置数据
再来看read过程,
可以猜到 read 过程其实就是write 过程的反向。这里同样还是从应用层asterisk出发来跟踪一下数据的来龙去脉。
前面已经知道write 会调用 dahdi_chan_write,
同样,read 其实是调用 dahdi_chan_read
来看一下 dahdi_chan_read
dahdi_chan_read
res = chan->outreadbuf;
if (copy_to_user(usrbuf, chan->readbuf[res], amnt)) // 从chan->readbuf 读取数据
return -EFAULT;
chan->readidx[res] = 0;
chan->readn[res] = 0;
oldbuf = res;
chan->outreadbuf = (res + 1) % chan->numbufs;
找到 __putbuf_chunk 函数中对chan->readbuf[res] 的处理
static void __putbuf_chunk(struct dahdi_chan *ss, unsigned char *rxb, int bytes)
while(bytes)
{
if (ms->inreadbuf > -1)
{
/* Read into the current buffer */
buf = ms->readbuf[ms->inreadbuf];
left = ms->blocksize - ms->readidx[ms->inreadbuf];
if (left > bytes)
left = bytes;
memcpy(buf + ms->readidx[ms->inreadbuf], rxb, left); //将 rxb 中数据读入 ms->readbuf
rxb += left;
ms->readidx[ms->inreadbuf] += left;
bytes -= left;
}
__putbuf_chunk 的调用关系:
dahdi_receive
ret = _dahdi_receive(span);
__dahdi_real_receive(chan);
__dahdi_receive_chunk(chan, chan->readchunk); // 将 chan->readchunk 中的数据 读入chan->readbuf
__dahdi_putbuf_chunk(chan, buf);
__putbuf_chunk(ss, rxb, DAHDI_CHUNKSIZE);
同样,dahdi_receive 由子模块驱动的中断处理函数调用。
chan->readchunk 中的数据由子模块填充,一般就是读取PCM中的数据,取出对应通道的数据进行填充。
伪代码如下:
for(i=0;i<8;i++)
{
for(chn = 0;chn < 8;chn++)
{
wc->chans[chn]->readchunk[i] = buf[chn*8+i];
}
}
dahdi_receive(&wc->span);
总结:asterisk 从chan_dahdi 读 pcm 数据过程
- 数据来源:PCM总线,如接的si3050
- dahdi 子驱动,中断程序读取PCM 数据并解析格式再写入到 wc->chans[chn]->readchunk
- 子驱动中断程序调用 dahdi_receive ,会将chans->readchunk 的数据写入chan->readbuf
- asterisk 调用 read 函数,其实最终是调用dahdi_chan_read ,将chan->readbuf 的数据copy到用户空间