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到用戶空間