新闻中心

EEPW首页>嵌入式系统>设计应用> Linux ALSA声卡驱动之三:PCM设备的创建

Linux ALSA声卡驱动之三:PCM设备的创建

作者: 时间:2016-12-14 来源:网络 收藏

  4. 设备文件节点的建立(dev/snd/pcmCxxDxxp、pcmCxxDxxc)

本文引用地址://m.amcfsurvey.com/article/201612/341593.htm

  4.1 struct snd_minor

  每个snd_minor结构体保存了声卡下某个逻辑设备的上下文信息,他在逻辑设备建立阶段被填充,在逻辑设备被使用时就可以从该结构体中得到相应的信息。pcm设备也不例外,也需要使用该结构体。该结构体在include/sound/core.h中定义。

  [c-sharp] view plain copystruct snd_minor {

  int type; /* SNDRV_DEVICE_TYPE_XXX */

  int card; /* card number */

  int device; /* device number */

  const struct file_operations *f_ops; /* file operations */

  void *private_data; /* private data for f_ops->open */

  struct device *dev; /* device for sysfs */

  };

  在sound/sound.c中定义了一个snd_minor指针的全局数组:

  [c-sharp] view plain copystatic struct snd_minor *snd_minors[256];

  前面说过,在声卡的注册阶段(snd_card_register),会调用pcm的回调函数snd_pcm_dev_register(),这个函数里会调用函数snd_register_device_for_dev():

  [c-sharp] view plain copystatic int snd_pcm_dev_register(struct snd_device *device)

  {

  ......

  /* register pcm */

  err = snd_register_device_for_dev(devtype, pcm->card,

  pcm->device,

  &snd_pcm_f_ops[cidx],

  pcm, str, dev);

  ......

  }

  我们再进入snd_register_device_for_dev():

  [c-sharp] view plain copyint snd_register_device_for_dev(int type, struct snd_card *card, int dev,

  const struct file_operations *f_ops,

  void *private_data,

  const char *name, struct device *device)

  {

  int minor;

  struct snd_minor *preg;

  if (snd_BUG_ON(!name))

  return -EINVAL;

  preg = kmalloc(sizeof *preg, GFP_KERNEL);

  if (preg == NULL)

  return -ENOMEM;

  preg->type = type;

  preg->card = card ? card->number : -1;

  preg->device = dev;

  preg->f_ops = f_ops;

  preg->private_data = private_data;

  mutex_lock(&sound_mutex);

  #ifdef CONFIG_SND_DYNAMIC_MINORS

  minor = snd_find_free_minor();

  #else

  minor = snd_kernel_minor(type, card, dev);

  if (minor >= 0 && snd_minors[minor])

  minor = -EBUSY;

  #endif

  if (minor < 0) {

  mutex_unlock(&sound_mutex);

  kfree(preg);

  return minor;

  }

  snd_minors[minor] = preg;

  preg->dev = device_create(sound_class, device, MKDEV(major, minor),

  private_data, "%s", name);

  if (IS_ERR(preg->dev)) {

  snd_minors[minor] = NULL;

  mutex_unlock(&sound_mutex);

  minor = PTR_ERR(preg->dev);

  kfree(preg);

  return minor;

  }

  mutex_unlock(&sound_mutex);

  return 0;

  }

  首先,分配并初始化一个snd_minor结构中的各字段

  type:SNDRV_DEVICE_TYPE_PCM_PLAYBACK/SNDRV_DEVICE_TYPE_PCM_CAPTURE

  card: card的编号

  device:pcm实例的编号,大多数情况为0

  f_ops:snd_pcm_f_ops

  private_data:指向该pcm的实例

  根据type,card和pcm的编号,确定数组的索引值minor,minor也作为pcm设备的此设备号

  把该snd_minor结构的地址放入全局数组snd_minors[minor]中

  最后,调用device_create创建设备节点

  4.2 设备文件的建立

  在4.1节的最后,设备文件已经建立,不过4.1节的重点在于snd_minors数组的赋值过程,在本节中,我们把重点放在设备文件中。

  回到pcm的回调函数snd_pcm_dev_register()中:

  [c-sharp] view plain copystatic int snd_pcm_dev_register(struct snd_device *device)

  {

  int cidx, err;

  char str[16];

  struct snd_pcm *pcm;

  struct device *dev;

  pcm = device->device_data;

  ......

  for (cidx = 0; cidx < 2; cidx++) {

  ......

  switch (cidx) {

  case SNDRV_PCM_STREAM_PLAYBACK:

  sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);

  devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;

  break;

  case SNDRV_PCM_STREAM_CAPTURE:

  sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);

  devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;

  break;

  }

  /* device pointer to use, pcm->dev takes precedence if

  * it is assigned, otherwise fall back to card's device

  * if possible */

  dev = pcm->dev;

  if (!dev)

  dev = snd_card_get_device_link(pcm->card);

  /* register pcm */

  err = snd_register_device_for_dev(devtype, pcm->card,

  pcm->device,

  &snd_pcm_f_ops[cidx],

  pcm, str, dev);

  ......

  }

  ......

  }

  以上代码我们可以看出,对于一个pcm设备,可以生成两个设备文件,一个用于playback,一个用于capture,代码中也确定了他们的命名规则:

  playback -- pcmCxDxp,通常系统中只有一各声卡和一个pcm,它就是pcmC0D0p

  capture -- pcmCxDxc,通常系统中只有一各声卡和一个pcm,它就是pcmC0D0c

  snd_pcm_f_ops

  snd_pcm_f_ops是一个标准的文件系统file_operations结构数组,它的定义在sound/core/pcm_native.c中:

  [c-sharp] view plain copyconst struct file_operations snd_pcm_f_ops[2] = {

  {

  .owner = THIS_MODULE,

  .write = snd_pcm_write,

  .aio_write = snd_pcm_aio_write,

  .open = snd_pcm_playback_open,

  .release = snd_pcm_release,

  .llseek = no_llseek,

  .poll = snd_pcm_playback_poll,

  .unlocked_ioctl = snd_pcm_playback_ioctl,

  .compat_ioctl = snd_pcm_ioctl_compat,

  .mmap = snd_pcm_mmap,

  .fasync = snd_pcm_fasync,

  .get_unmapped_area = snd_pcm_get_unmapped_area,

  },

  {

  .owner = THIS_MODULE,

  .read = snd_pcm_read,

  .aio_read = snd_pcm_aio_read,

  .open = snd_pcm_capture_open,

  .release = snd_pcm_release,

  .llseek = no_llseek,

  .poll = snd_pcm_capture_poll,

  .unlocked_ioctl = snd_pcm_capture_ioctl,

  .compat_ioctl = snd_pcm_ioctl_compat,

  .mmap = snd_pcm_mmap,

  .fasync = snd_pcm_fasync,

  .get_unmapped_area = snd_pcm_get_unmapped_area,

  }

  };

  snd_pcm_f_ops作为snd_register_device_for_dev的参数被传入,并被记录在snd_minors[minor]中的字段f_ops中。最后,在snd_register_device_for_dev中创建设备节点:

  [c-sharp] view plain copysnd_minors[minor] = preg;

  preg->dev = device_create(sound_class, device, MKDEV(major, minor),

  private_data, "%s", name);

  4.3 层层深入,从应用程序到驱动层pcm

  4.3.1 字符设备注册

  在sound/core/sound.c中有alsa_sound_init()函数,定义如下:

  [c-sharp] view plain copystatic int __init alsa_sound_init(void)

  {

  snd_major = major;

  snd_ecards_limit = cards_limit;

  if (register_chrdev(major, "alsa", &snd_fops)) {

  snd_printk(KERN_ERR "unable to register native major device number %d/n", major);

  return -EIO;

  }

  if (snd_info_init() < 0) {

  unregister_chrdev(major, "alsa");

  return -ENOMEM;

  }

  snd_info_minor_register();

  return 0;

  }

  register_chrdev中的参数major与之前创建pcm设备是device_create时的major是同一个,这样的结果是,当应用程序open设备文件/dev/snd/pcmCxDxp时,会进入snd_fops的open回调函数,我们将在下一节中讲述open的过程。

  4.3.2 打开pcm设备

  从上一节中我们得知,open一个pcm设备时,将会调用snd_fops的open回调函数,我们先看看snd_fops的定义:

  [c-sharp] view plain copystatic const struct file_operations snd_fops =

  {

  .owner = THIS_MODULE,

  .open = snd_open

  };

  跟入snd_open函数,它首先从inode中取出此设备号,然后以次设备号为索引,从snd_minors全局数组中取出当初注册pcm设备时填充的snd_minor结构(参看4.1节的内容),然后从snd_minor结构中取出pcm设备的f_ops,并且把file->f_op替换为pcm设备的f_ops,紧接着直接调用pcm设备的f_ops->open(),然后返回。因为file->f_op已经被替换,以后,应用程序的所有read/write/ioctl调用都会进入pcm设备自己的回调函数中,也就是4.2节中提到的snd_pcm_f_ops结构中定义的回调。

  [c-sharp] view plain copystatic int snd_open(struct inode *inode, struct file *file)

  {

  unsigned int minor = iminor(inode);

  struct snd_minor *mptr = NULL;

  const struct file_operations *old_fops;

  int err = 0;

  if (minor >= ARRAY_SIZE(snd_minors))

  return -ENODEV;

  mutex_lock(&sound_mutex);

  mptr = snd_minors[minor];

  if (mptr == NULL) {

  mptr = autoload_device(minor);

  if (!mptr) {

  mutex_unlock(&sound_mutex);

  return -ENODEV;

  }

  }

  old_fops = file->f_op;

  file->f_op = fops_get(mptr->f_ops);

  if (file->f_op == NULL) {

  file->f_op = old_fops;

  err = -ENODEV;

  }

  mutex_unlock(&sound_mutex);

  if (err < 0)

  return err;

  if (file->f_op->open) {

  err = file->f_op->open(inode, file);

  if (err) {

  fops_put(file->f_op);

  file->f_op = fops_get(old_fops);

  }

  }

  fops_put(old_fops);

  return err;

  }

  下面的序列图展示了应用程序如何最终调用到snd_pcm_f_ops结构中的回调函数:

  图4.3.2.1 应用程序操作pcm设备


上一页 1 2 下一页

关键词:LinuxALSA

评论


相关推荐

技术专区

关闭