Linux ALSA驱动之四:Control设备创建流程源码分析(5.18)下

简介: Linux ALSA驱动之四:Control设备创建流程源码分析(5.18)下

Linux ALSA驱动之四:Control设备创建流程源码分析(5.18)下


5、函数详解

5.1、snd_ctl_new1函数

/**
 * snd_ctl_new1 - create a control instance from the template
 * @ncontrol: the initialization record
 * @private_data: the private data to set
 *
 * Allocates a new struct snd_kcontrol instance and initialize from the given
 * template.  When the access field of ncontrol is 0, it's assumed as
 * READWRITE access. When the count field is 0, it's assumes as one.
 *
 * Return: The pointer of the newly generated instance, or %NULL on failure.
 */
struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
          void *private_data)
{
  struct snd_kcontrol *kctl;
  unsigned int count;
  unsigned int access;
  int err;
  if (snd_BUG_ON(!ncontrol || !ncontrol->info))
    return NULL;
  count = ncontrol->count;
  if (count == 0)
    count = 1;
  access = ncontrol->access;
  if (access == 0)
    access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
  access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE |
       SNDRV_CTL_ELEM_ACCESS_VOLATILE |
       SNDRV_CTL_ELEM_ACCESS_INACTIVE |
       SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE |
       SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND |
       SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK |
       SNDRV_CTL_ELEM_ACCESS_LED_MASK |
       SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK);
    /* 创建snd_kcontrol */
  err = snd_ctl_new(&kctl, count, access, NULL);
  if (err < 0)
    return NULL;
    /* 根据snd_kcontrol_new初始化snd_kcontrol */
  /* The 'numid' member is decided when calling snd_ctl_add(). */
  kctl->id.iface = ncontrol->iface;
  kctl->id.device = ncontrol->device;
  kctl->id.subdevice = ncontrol->subdevice;
  if (ncontrol->name) {
    strscpy(kctl->id.name, ncontrol->name, sizeof(kctl->id.name));
    if (strcmp(ncontrol->name, kctl->id.name) != 0)
      pr_warn("ALSA: Control name '%s' truncated to '%s'\n",
        ncontrol->name, kctl->id.name);
  }
  kctl->id.index = ncontrol->index;
  kctl->info = ncontrol->info;
  kctl->get = ncontrol->get;
  kctl->put = ncontrol->put;
  kctl->tlv.p = ncontrol->tlv.p;
  kctl->private_value = ncontrol->private_value;
  kctl->private_data = private_data;
  return kctl;
}

分配一个新的snd_kcontrol实例,并把my_control中相应的值复制到该实例中,所以,在定义my_control时,通常我们可以加上__devinitdata前缀.snd_ctl_add则把该control绑定到声卡对象card当中。

struct snd_kcontrol {
  struct list_head list;                    /* list of controls */
  struct snd_ctl_elem_id id;
  unsigned int count;                       /* count of same elements */
  snd_kcontrol_info_t *info;
  snd_kcontrol_get_t *get;
  snd_kcontrol_put_t *put;
  union {
    snd_kcontrol_tlv_rw_t *c;
    const unsigned int *p;
  } tlv;
  unsigned long private_value;
  void *private_data;
  void (*private_free)(struct snd_kcontrol *kcontrol);
  struct snd_kcontrol_volatile vd[];          /* volatile data */
};
#define snd_kcontrol(n) list_entry(n, struct snd_kcontrol, list)

5.2、 snd_ctl_add函数

/* add/replace a new kcontrol object; call with card->controls_rwsem locked */
static int __snd_ctl_add_replace(struct snd_card *card,
         struct snd_kcontrol *kcontrol,
         enum snd_ctl_add_mode mode)
{
  struct snd_ctl_elem_id id;
  unsigned int idx;
  struct snd_kcontrol *old;
  int err;
  id = kcontrol->id;
  if (id.index > UINT_MAX - kcontrol->count)
    return -EINVAL;
  old = snd_ctl_find_id(card, &id);
  if (!old) {
    if (mode == CTL_REPLACE)
      return -EINVAL;
  } else {
    if (mode == CTL_ADD_EXCLUSIVE) {
      dev_err(card->dev,
        "control %i:%i:%i:%s:%i is already present\n",
        id.iface, id.device, id.subdevice, id.name,
        id.index);
      return -EBUSY;
    }
    err = snd_ctl_remove(card, old);
    if (err < 0)
      return err;
  }
  if (snd_ctl_find_hole(card, kcontrol->count) < 0)
    return -ENOMEM;
    /* 把snd_kcontrol挂入snd_card的controls链表 */
  list_add_tail(&kcontrol->list, &card->controls);
  card->controls_count += kcontrol->count;
    /* 设置元素ID */
  kcontrol->id.numid = card->last_numid + 1;
  card->last_numid += kcontrol->count;
  for (idx = 0; idx < kcontrol->count; idx++)
    snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_ADD, kcontrol, idx);
  return 0;
}
static int snd_ctl_add_replace(struct snd_card *card,
             struct snd_kcontrol *kcontrol,
             enum snd_ctl_add_mode mode)
{
  int err = -EINVAL;
  if (! kcontrol)
    return err;
  if (snd_BUG_ON(!card || !kcontrol->info))
    goto error;
  down_write(&card->controls_rwsem);
  err = __snd_ctl_add_replace(card, kcontrol, mode);
  up_write(&card->controls_rwsem);
  if (err < 0)
    goto error;
  return 0;
 error:
  snd_ctl_free_one(kcontrol);
  return err;
}
/**
 * snd_ctl_add - add the control instance to the card
 * @card: the card instance
 * @kcontrol: the control instance to add
 *
 * Adds the control instance created via snd_ctl_new() or
 * snd_ctl_new1() to the given card. Assigns also an unique
 * numid used for fast search.
 *
 * It frees automatically the control which cannot be added.
 *
 * Return: Zero if successful, or a negative error code on failure.
 *
 */
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
{
  return snd_ctl_add_replace(card, kcontrol, CTL_ADD_EXCLUSIVE);
}

5.3、info回调函数

       用于得到对应control的详细信息,需要把信息存入snd_ctl_elem_info 对象中。

struct snd_ctl_elem_info {
  struct snd_ctl_elem_id id;  /* W: element ID */
  snd_ctl_elem_type_t type; /* R: value type - SNDRV_CTL_ELEM_TYPE_* */
  unsigned int access;    /* R: value access (bitmask) - SNDRV_CTL_ELEM_ACCESS_* */
  unsigned int count;   /* count of values */
  __kernel_pid_t owner;   /* owner's PID of this control */
  union {
    struct {
      long min;   /* R: minimum value */
      long max;   /* R: maximum value */
      long step;    /* R: step (0 variable) */
    } integer;
    struct {
      long long min;    /* R: minimum value */
      long long max;    /* R: maximum value */
      long long step;   /* R: step (0 variable) */
    } integer64;
    struct {
      unsigned int items; /* R: number of items */
      unsigned int item;  /* W: item number */
      char name[64];    /* R: value name */
      __u64 names_ptr;  /* W: names list (ELEM_ADD only) */
      unsigned int names_length;
    } enumerated;
    unsigned char reserved[128];
  } value;
  unsigned char reserved[64];
};

其中的value是个一个共用体,需要根据control的类型,确定值的类型,control type包括如下几类:

typedef int __bitwise snd_ctl_elem_type_t;
#define SNDRV_CTL_ELEM_TYPE_NONE  ((__force snd_ctl_elem_type_t) 0)     /* invalid */
#define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((__force snd_ctl_elem_type_t) 1)     /* boolean type */
#define SNDRV_CTL_ELEM_TYPE_INTEGER ((__force snd_ctl_elem_type_t) 2)     /* integer type */
#define SNDRV_CTL_ELEM_TYPE_ENUMERATED  ((__force snd_ctl_elem_type_t) 3) /* enumerated type */
#define SNDRV_CTL_ELEM_TYPE_BYTES ((__force snd_ctl_elem_type_t) 4)     /* byte array */
#define SNDRV_CTL_ELEM_TYPE_IEC958  ((__force snd_ctl_elem_type_t) 5)     /* IEC958 (S/PDIF) setup */
#define SNDRV_CTL_ELEM_TYPE_INTEGER64 ((__force snd_ctl_elem_type_t) 6) /* 64-bit integer type */
#define SNDRV_CTL_ELEM_TYPE_LAST  SNDRV_CTL_ELEM_TYPE_INTEGER64

下面是以SNDRV_CTL_ELEM_TYPE_INTEGER和以SNDRV_CTL_ELEM_TYPE_BOOLEAN为例定义的info回调函数:

static int snd_cx88_volume_info(struct snd_kcontrol *kcontrol,
        struct snd_ctl_elem_info *info)
{
  info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
  info->count = 2;
  info->value.integer.min = 0;
  info->value.integer.max = 0x3f;
  return 0;
}
static int snd_saa7134_capsrc_info(struct snd_kcontrol * kcontrol,
           struct snd_ctl_elem_info * uinfo)
{
  uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
  uinfo->count = 2;
  uinfo->value.integer.min = 0;
  uinfo->value.integer.max = 1;
  return 0;
}

5.4、get回调函数

       这个函数用来读取当前 control 的值并返回到用户空间,需要把值放在snd_ctl_elem_value结构体中,与info结构体类似,value字段是一个共用体,与类型相关。如果value的cont大于1, 需要把值全部放入到 value[]数组中。

static int snd_cx88_volume_info(struct snd_kcontrol *kcontrol,
        struct snd_ctl_elem_info *info)
{
  info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
  info->count = 2;
  info->value.integer.min = 0;
  info->value.integer.max = 0x3f;
  return 0;
}
static int snd_cx88_volume_get(struct snd_kcontrol *kcontrol,
             struct snd_ctl_elem_value *value)
{
  struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
  struct cx88_core *core = chip->core;
  int vol = 0x3f - (cx_read(AUD_VOL_CTL) & 0x3f),
      bal = cx_read(AUD_BAL_CTL);
  value->value.integer.value[(bal & 0x40) ? 0 : 1] = vol;
  vol -= (bal & 0x3f);
  value->value.integer.value[(bal & 0x40) ? 1 : 0] = vol < 0 ? 0 : vol;
  return 0;
}

5.5、put回调函数

        put回调函数用于把应用程序的控制值设置到control中。

static void snd_cx88_wm8775_volume_put(struct snd_kcontrol *kcontrol,
               struct snd_ctl_elem_value *value)
{
  struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
  struct cx88_core *core = chip->core;
  u16 left = value->value.integer.value[0];
  u16 right = value->value.integer.value[1];
  int v, b;
  /* Pass volume & balance onto any WM8775 */
  if (left >= right) {
    v = left << 10;
    b = left ? (0x8000 * right) / left : 0x8000;
  } else {
    v = right << 10;
    b = right ? 0xffff - (0x8000 * left) / right : 0x8000;
  }
  wm8775_s_ctrl(core, V4L2_CID_AUDIO_VOLUME, v);
  wm8775_s_ctrl(core, V4L2_CID_AUDIO_BALANCE, b);
}
/* OK - TODO: test it */
static int snd_cx88_volume_put(struct snd_kcontrol *kcontrol,
             struct snd_ctl_elem_value *value)
{
  struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
  struct cx88_core *core = chip->core;
  int left, right, v, b;
  int changed = 0;
  u32 old;
  if (core->sd_wm8775)
    snd_cx88_wm8775_volume_put(kcontrol, value);
  left = value->value.integer.value[0] & 0x3f;
  right = value->value.integer.value[1] & 0x3f;
  b = right - left;
  if (b < 0) {
    v = 0x3f - left;
    b = (-b) | 0x40;
  } else {
    v = 0x3f - right;
  }
  /* Do we really know this will always be called with IRQs on? */
  spin_lock_irq(&chip->reg_lock);
  old = cx_read(AUD_VOL_CTL);
  if (v != (old & 0x3f)) {
    cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, (old & ~0x3f) | v);
    changed = 1;
  }
  if ((cx_read(AUD_BAL_CTL) & 0x7f) != b) {
    cx_write(AUD_BAL_CTL, b);
    changed = 1;
  }
  spin_unlock_irq(&chip->reg_lock);
  return changed;
}

6、Control设备创建流程

       Control设备和PCM设备一样,都属于声卡下的逻辑设备。用户空间的应用程序通过alsa-lib访问该Control设备,读取或设置control的控制状态,从而达到控制音频Codec进行各种Mixer等控制操作。

       Control设备的创建过程大体上和PCM设备的创建过程相同。详细的创建过程可以参考下方时序图。

     我们需要在我们的驱动程序初始化时主动调用snd_pcm_new()函数创建pcm设备,而control设备则在snd_ctl_new1()内被创建,snd_ctl_new1()通过调用snd_ctl_create()函数创建control设备节点。所以我们无需显式地创建control设备,只要建立声卡,control设备被自动地创建。

     和pcm设备一样,control设备的名字遵循一定的规则:controlCxx,这里的xx代表声卡的编号。

       snd_ctl_dev_register()函数会在snd_card_register()中,即声卡的注册阶段被调用。注册完成后,control设备的相关信息被保存在snd_minors[]数组中,用control设备的次设备号作索引,即可在snd_minors[]数组中找出相关的信息。注册完成后的数据结构关系可以用下图进行表述:

相关文章
|
11天前
|
安全 Linux 网络安全
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
42 0
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
|
2月前
|
存储 Linux
Linux环境下删除大文件后磁盘空间未释放问题诊断流程。
以上诊断流程涉及Linux底层机制与高级管理技能结合之处,并需要管理员根据实际环境灵活调整诊断策略与解决方案。
119 8
|
2月前
|
数据采集 编解码 运维
一文讲完说懂 WowKey -- WowKey 是一款 Linux 类设备的命令行(CLT)运维工具
WowKey 是一款面向 Linux 类设备的命令行运维工具,支持自动登录、批量执行及标准化维护,适用于企业、团队或个人管理多台设备,显著提升运维效率与质量。
|
3月前
|
监控 Linux 开发者
理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
172 0
|
5月前
|
安全 Ubuntu Linux
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
158 0
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
|
6月前
|
运维 安全 Linux
试试Linux设备命令行运维工具——Wowkey
WowKey 是一款专为 Linux 设备设计的命令行运维工具,提供自动化、批量化、标准化、简单化的运维解决方案。它简单易用、高效集成且无依赖,仅需 WIS 指令剧本文件、APT 账号密码文件和 wowkey 命令即可操作。通过分离鉴权内容与执行内容,WowKey 让运维人员专注于决策,摆脱繁琐的交互与执行细节工作,大幅提升运维效率与质量。无论是健康检查、数据采集还是配置更新,WowKey 都能助您轻松应对大规模设备运维挑战。立即从官方资源了解更多信息:https://atsight.top/training。
|
6月前
|
数据采集 运维 安全
Linux设备命令行运维工具WowKey问答
WowKey 是一款用于 Linux 设备运维的工具,可通过命令行手动或自动执行指令剧本,实现批量、标准化操作,如健康检查、数据采集、配置更新等。它简单易用,只需编写 WIS 指令剧本和 APT 帐号密码表文件,学习成本极低。支持不同流派的 Linux 系统,如 RHEL、Debian、SUSE 等,只要使用通用 Shell 命令即可通吃Linux设备。
|
27天前
|
Unix Linux 程序员
Linux文本搜索工具grep命令使用指南
以上就是对Linux环境下强大工具 `grep` 的基础到进阶功能介绍。它不仅能够执行简单文字查询任务还能够处理复杂文字处理任务,并且支持强大而灵活地正则表达规范来增加查询精度与效率。无论您是程序员、数据分析师还是系统管理员,在日常工作中熟练运用该命令都将极大提升您处理和分析数据效率。
103 16
|
19天前
|
Linux
linux命令—stat
`stat` 是 Linux 系统中用于查看文件或文件系统详细状态信息的命令。相比 `ls -l`,它提供更全面的信息,包括文件大小、权限、所有者、时间戳(最后访问、修改、状态变更时间)、inode 号、设备信息等。其常用选项包括 `-f` 查看文件系统状态、`-t` 以简洁格式输出、`-L` 跟踪符号链接,以及 `-c` 或 `--format` 自定义输出格式。通过这些选项,用户可以灵活获取所需信息,适用于系统调试、权限检查、磁盘管理等场景。
|
3月前
|
监控 Linux 网络安全
Linux命令大全:从入门到精通
日常使用的linux命令整理
668 13