DAHDI 驅動學習筆記(一)與Asterisk之間的PCM數據交互

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到用戶空間
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章