Linux USB 3.0驅動分析(七)——UAC驅動分析



因為項目里面有USB音頻外設,所以需要分析一下UAC驅動。
USB Audio Class,USB音頻類,一個像USB這樣的通用數據接口,可以有很多種實現數字音頻數據傳輸的方式。不同的開發者可以根據自己的喜好和需求,定義任意的控制方式,傳輸模式,音頻格式等等參數。


一.UAC驅動初始化分析
  代碼路徑:sound\usb\card.c
我們先來看看初始化部分,主要是初始化usb_audio_driver結構體
static const struct usb_device_id usb_audio_ids [] = { //這里是匹配列表
#include "quirks-table.h" //這個文件里面有很多廠商的聲卡,主要是根據不同的聲卡,進行一些特殊設置和處理
    { .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS),
      .bInterfaceClass = USB_CLASS_AUDIO,
      .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL },
    { }                        /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, usb_audio_ids);
/*
 * entry point for linux usb interface
 */
static struct usb_driver usb_audio_driver = {
    .name =        "snd-usb-audio",
    .probe =    usb_audio_probe, //匹配成功后進入這個函數,匹配過程前面的有說明
    .disconnect =    usb_audio_disconnect,
    .suspend =    usb_audio_suspend,
    .resume =    usb_audio_resume,
    .reset_resume =    usb_audio_reset_resume,
    .id_table =    usb_audio_ids,
    .supports_autosuspend = 1, 
};

module_usb_driver(usb_audio_driver); //這里封裝了,用usb_register注冊一個接口驅動
匹配過后,進入usb_audio_probe函數里面
static int usb_audio_probe(struct usb_interface *intf,
               const struct usb_device_id *usb_id)
{
    alts = &intf->altsetting[0]; //獲取當前配置描述符
    ifnum = get_iface_desc(alts)->bInterfaceNumber; //獲取接口的個數
    id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
            le16_to_cpu(dev->descriptor.idProduct));
    if (get_alias_id(dev, &id))
        quirk = get_alias_quirk(dev, id);
    if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum)
        return -ENXIO;

    err = snd_usb_apply_boot_quirk(dev, intf, quirk, id); //根據不同的聲卡設備,進行一些操作,也就是相當於補丁
    if (err < 0)
        return err;
    /*
     * found a config.  now register to ALSA
     */
    if (! chip) { //如果沒有注冊
        /* it's a fresh one.
         * now look for an empty slot and create a new card instance
         */
        for (i = 0; i < SNDRV_CARDS; i++) //最多注冊8個
            if (!usb_chip[i] &&
                (vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) &&
                (pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) {
                if (enable[i]) {
                    err = snd_usb_audio_create(intf, dev, i, quirk,
                                   id, &chip); //創建一個chip實例並設置它的名稱,主要調用snd_card_new創建並初始化一個聲卡結構
               .........
            }
    }
    err = 1; /* continue */
    if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) {
        /* need some special handlings */ //需要一些特殊處理
        err = snd_usb_create_quirk(chip, intf, &usb_audio_driver, quirk);
        if (err < 0)
            goto __error;
    }

    if (err > 0) {
        /* create normal USB audio interfaces */
        err = snd_usb_create_streams(chip, ifnum); //解析音頻控制描述符並創建pcm/midi流
        if (err < 0)
            goto __error;
        err = snd_usb_create_mixer(chip, ifnum, ignore_ctl_error); //解析音頻控制描述符並創建mixer控制節點
        if (err < 0)
            goto __error;
    }

    /* we are allowed to call snd_card_register() many times */
    err = snd_card_register(chip->card); //注冊一個聲卡
    if (err < 0)
        goto __error;
}
我們重點分析一下snd_usb_create_streams,它主要調用snd_usb_create_stream,主要是根據不同的協議,進入不同的分支

static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int interface)
{
    alts = &iface->altsetting[0]; //獲取當前設置描述符
    altsd = get_iface_desc(alts); //獲取接口描述符

    /*
     * Android with both accessory and audio interfaces enabled gets the
     * interface numbers wrong.
     */
    if ((chip->usb_id == USB_ID(0x18d1, 0x2d04) ||
         chip->usb_id == USB_ID(0x18d1, 0x2d05)) && //主要針對於安卓
        interface == 0 &&
        altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC &&
        altsd->bInterfaceSubClass == USB_SUBCLASS_VENDOR_SPEC) {
        interface = 2;
        iface = usb_ifnum_to_if(dev, interface);
        if (!iface)
            return -EINVAL;
        alts = &iface->altsetting[0];
        altsd = get_iface_desc(alts);
    }

    if ((altsd->bInterfaceClass == USB_CLASS_AUDIO ||
         altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) &&
        altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) { //如果是midi接口
        int err = __snd_usbmidi_create(chip->card, iface,
                         &chip->midi_list, NULL,
                         chip->usb_id);
        if (err < 0) {
            dev_err(&dev->dev,
                "%u:%d: cannot create sequencer device\n",
                ctrlif, interface);
            return -EINVAL;
        }
        usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
        return 0;
    }

    if (snd_usb_get_speed(dev) == USB_SPEED_LOW) { //UAV不知道低速設備
        dev_err(&dev->dev, "low speed audio streaming not supported\n");
        return -EINVAL;
    }

    if (! snd_usb_parse_audio_interface(chip, interface)) { //解析USB音頻接口
        usb_set_interface(dev, interface, 0); /* reset the current interface */
        usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L); //綁定一個驅動到接口
    }

    return 0;
}
這里面有一個重要的函數,snd_usb_parse_audio_interface,用來解析音頻接口,也就是pcm。這里面UAC1.0,2.0和UAC3.0有些一樣。
UAC1.0,2.0使用snd_usb_add_audio_stream,UAC3.0使用snd_usb_add_audio_stream_v3。我們只看UAC1.0,2.0
大概的流程是
snd_usb_parse_audio_interface -> snd_usb_parse_audio_interface 
static int __snd_usb_parse_audio_interface(struct snd_usb_audio *chip,
                       int iface_no,
                       bool *has_non_pcm, bool non_pcm)
{
    for (i = 0; i < num; i++) {
        alts = &iface->altsetting[i];
        altsd = get_iface_desc(alts);
        protocol = altsd->bInterfaceProtocol;
        switch (protocol) {
        default:
            dev_dbg(&dev->dev, "%u:%d: unknown interface protocol %#02x, assuming v1\n",
                iface_no, altno, protocol);
            protocol = UAC_VERSION_1;
            /* fall through */
        case UAC_VERSION_1:
            /* fall through */
        case UAC_VERSION_2: {
            int bm_quirk = 0;
            /*
             * Blue Microphones workaround: The last altsetting is
             * identical with the previous one, except for a larger
             * packet size, but is actually a mislabeled two-channel
             * setting; ignore it.
             *
             * Part 1: prepare quirk flag
             */
            if (altno == 2 && num == 3 &&
                fp && fp->altsetting == 1 && fp->channels == 1 &&
                fp->formats == SNDRV_PCM_FMTBIT_S16_LE &&
                protocol == UAC_VERSION_1 &&
                le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) ==
                            fp->maxpacksize * 2)
                bm_quirk = 1;
            fp = snd_usb_get_audioformat_uac12(chip, alts, protocol,
                               iface_no, i, altno,
                               stream, bm_quirk); //獲取UAC12的音頻格式
            break;
        }
        case UAC_VERSION_3:
            fp = snd_usb_get_audioformat_uac3(chip, alts, &pd,
                        iface_no, i, altno, stream); //獲取UAC3的音頻格式
            break;
        }
        dev_dbg(&dev->dev, "%u:%d: add audio endpoint %#x\n", iface_no, altno, fp->endpoint);
        if (protocol == UAC_VERSION_3)
            err = snd_usb_add_audio_stream_v3(chip, stream, fp, pd); //UAC3添加audio stream
        else
            err = snd_usb_add_audio_stream(chip, stream, fp); //UAC12添加audio stream

        /* try to set the interface... */
        usb_set_interface(chip->dev, iface_no, altno);
        snd_usb_init_pitch(chip, iface_no, alts, fp);
        snd_usb_init_sample_rate(chip, iface_no, alts, fp, fp->rate_max); //初始化采樣率
    }
    return 0;
}
snd_usb_add_audio_stream和snd_usb_add_audio_stream_v3都會調用__snd_usb_add_audio_stream,這里創建stream. 該接口函數中根據stream的類型會分別創建playback_stream和capture_stream,並分別創建對應的playback的pcm接口和capture的接口:
static int __snd_usb_add_audio_stream(struct snd_usb_audio *chip,
                      int stream,
                      struct audioformat *fp,
                      struct snd_usb_power_domain *pd)


{
    struct snd_usb_stream *as;
    struct snd_usb_substream *subs;
    struct snd_pcm *pcm;
    int err;

    list_for_each_entry(as, &chip->pcm_list, list) { //如果具有相同端點的流已經存在,則對其進行追加
        if (as->fmt_type != fp->fmt_type)
            continue;
        subs = &as->substream[stream];
        if (subs->ep_num == fp->endpoint) {
            list_add_tail(&fp->list, &subs->fmt_list);
            subs->num_formats++;
            subs->formats |= fp->formats;
            return 0;
        }
    }
    /* look for an empty stream */
    list_for_each_entry(as, &chip->pcm_list, list) {
        if (as->fmt_type != fp->fmt_type)
            continue;
        subs = &as->substream[stream];
        if (subs->ep_num)
            continue;
        err = snd_pcm_new_stream(as->pcm, stream, 1);
        if (err < 0)
            return err;
        snd_usb_init_substream(as, stream, fp, pd);
        return add_chmap(as->pcm, stream, subs);
    }


    /* create a new pcm */
    as = kzalloc(sizeof(*as), GFP_KERNEL);
    if (!as)
        return -ENOMEM;
    as->pcm_index = chip->pcm_devs;
    as->chip = chip;
    as->fmt_type = fp->fmt_type;
    err = snd_pcm_new(chip->card, "USB Audio", chip->pcm_devs,
              stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0,
              stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1,
              &pcm); //創建一個新的PCM實例
    if (err < 0) {
        kfree(as);
        return err;
    }
    as->pcm = pcm;
    pcm->private_data = as;
    pcm->private_free = snd_usb_audio_pcm_free;
    pcm->info_flags = 0;
    if (chip->pcm_devs > 0)
        sprintf(pcm->name, "USB Audio #%d", chip->pcm_devs);
    else
        strcpy(pcm->name, "USB Audio");

    snd_usb_init_substream(as, stream, fp, pd); //創建一個neinitialize子流實例PCM實例
    /*
     * Keep using head insertion for M-Audio Audiophile USB (tm) which has a
     * fix to swap capture stream order in conf/cards/USB-audio.conf
     */
    if (chip->usb_id == USB_ID(0x0763, 0x2003))
        list_add(&as->list, &chip->pcm_list);
    else
        list_add_tail(&as->list, &chip->pcm_list);
    chip->pcm_devs++;
    snd_usb_proc_pcm_format_add(as);

    return add_chmap(pcm, stream, &as->substream[stream]);
}
其中最重要的是snd_usb_set_pcm_ops()函數,它設置了pcm_substream的操作接口函數.
static const struct snd_pcm_ops snd_usb_playback_ops = {
    .open =        snd_usb_pcm_open,
    .close =    snd_usb_pcm_close,
    .ioctl =    snd_pcm_lib_ioctl,
    .hw_params =    snd_usb_hw_params,
    .hw_free =    snd_usb_hw_free,
    .prepare =    snd_usb_pcm_prepare,
    .trigger =    snd_usb_substream_playback_trigger,
    .pointer =    snd_usb_pcm_pointer,
    .page =        snd_pcm_lib_get_vmalloc_page,
};


static const struct snd_pcm_ops snd_usb_capture_ops = {
    .open =        snd_usb_pcm_open,
    .close =    snd_usb_pcm_close,
    .ioctl =    snd_pcm_lib_ioctl,
    .hw_params =    snd_usb_hw_params,
    .hw_free =    snd_usb_hw_free,
    .prepare =    snd_usb_pcm_prepare,
    .trigger =    snd_usb_substream_capture_trigger,
    .pointer =    snd_usb_pcm_pointer,
    .page =        snd_pcm_lib_get_vmalloc_page,
};


static const struct snd_pcm_ops snd_usb_playback_dev_ops = {
    .open =        snd_usb_pcm_open,
    .close =    snd_usb_pcm_close,
    .ioctl =    snd_pcm_lib_ioctl,
    .hw_params =    snd_usb_hw_params,
    .hw_free =    snd_usb_hw_free,
    .prepare =    snd_usb_pcm_prepare,
    .trigger =    snd_usb_substream_playback_trigger,
    .pointer =    snd_usb_pcm_pointer,
    .page =        snd_pcm_sgbuf_ops_page,
};


static const struct snd_pcm_ops snd_usb_capture_dev_ops = {
    .open =        snd_usb_pcm_open,
    .close =    snd_usb_pcm_close,
    .ioctl =    snd_pcm_lib_ioctl,
    .hw_params =    snd_usb_hw_params,
    .hw_free =    snd_usb_hw_free,
    .prepare =    snd_usb_pcm_prepare,
    .trigger =    snd_usb_substream_capture_trigger,
    .pointer =    snd_usb_pcm_pointer,
    .page =        snd_pcm_sgbuf_ops_page,
};


void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream)
{
    const struct snd_pcm_ops *ops;


    if (snd_usb_use_vmalloc) //默認一般是1
        ops = stream == SNDRV_PCM_STREAM_PLAYBACK ?
            &snd_usb_playback_ops : &snd_usb_capture_ops; //操作函數賦值
    else
        ops = stream == SNDRV_PCM_STREAM_PLAYBACK ?
            &snd_usb_playback_dev_ops : &snd_usb_capture_dev_ops;
    snd_pcm_set_ops(pcm, stream, ops);
}
snd_usb_use_vmalloc默認是1,上面函數給pcm->substream->ops賦值,根據stream類型,分別賦值為snd_usb_playback_ops 或者 snd_usb_capture_ops。到這里初始化基本上已經完成了。


二.UAC驅動讀寫分析
ALSA音頻播放之前已經有分析過來,具體參考《  rk音頻驅動分析之tinyplay播放
前面部分都是一樣的,只是調用到不同的 substream調用不同的 substream的ops函數

1.open
應用打開設備: open(fn, O_RDWR);   //比如是 /dev/snd/pcmC0D0p
對於UAC設備通過一系列掉用就會到snd_usb_pcm_open
主要是一些結構體初始化,還有建立硬件方面的信息。snd_usb_stream下面有snd_usb_substream,snd_usb_substream下面有snd_pcm_substream
ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info) //獲取PCM信息static int snd_usb_pcm_open(struct snd_pcm_substream *substream)
{
    int direction = substream->stream;
    struct snd_usb_stream *as = snd_pcm_substream_chip(substream);
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct snd_usb_substream *subs = &as->substream[direction];
    int ret;
    
    subs->interface = -1;
    subs->altset_idx = 0;
    runtime->hw = snd_usb_hardware;
    runtime->private_data = subs;
    subs->pcm_substream = substream;
    /* runtime PM is also done there */
    /* initialize DSD/DOP context */
    subs->dsd_dop.byte_idx = 0;
    subs->dsd_dop.channel = 0;
    subs->dsd_dop.marker = 1;

    ret = setup_hw_info(runtime, subs); //設置運行時硬件信息,比如采樣率,通道等
    if (ret == 0) {
        ret = snd_media_stream_init(subs, as->pcm, direction);
        if (ret)
            snd_usb_autosuspend(subs->stream->chip);
    }
    return ret;
}

2.設置音頻參數
應用調用ioctl:ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params)  //設置音頻硬件參數
對於UAC設備通過一系列掉用就會到snd_usb_hw_params,主要是設置一些音頻參數,還有接口和端點方面的設置。
static int snd_usb_hw_params(struct snd_pcm_substream *substream,
                 struct snd_pcm_hw_params *hw_params)
{
    struct snd_usb_substream *subs = substream->runtime->private_data;
    struct audioformat *fmt;
    int ret;

    ret = snd_media_start_pipeline(subs);
    if (ret)
        return ret;

    if (snd_usb_use_vmalloc)
        ret = snd_pcm_lib_alloc_vmalloc_buffer(substream,
                               params_buffer_bytes(hw_params)); //分配虛擬DMA內存,只能被內核代碼使用,不能被DMA設備使用
    else
        ret = snd_pcm_lib_malloc_pages(substream,
                           params_buffer_bytes(hw_params));
    if (ret < 0)
        goto stop_pipeline;

    subs->pcm_format = params_format(hw_params); //格式
    subs->period_bytes = params_period_bytes(hw_params); //周期
    subs->period_frames = params_period_size(hw_params); 
    subs->buffer_periods = params_periods(hw_params);
    subs->channels = params_channels(hw_params);
    subs->cur_rate = params_rate(hw_params);

    fmt = find_format(subs); //從snd_usb_substream找到對應的格式,找不到就報錯
    if (!fmt) {
        dev_dbg(&subs->dev->dev,
            "cannot set format: format = %#x, rate = %d, channels = %d\n",
               subs->pcm_format, subs->cur_rate, subs->channels);
        ret = -EINVAL;
        goto stop_pipeline;
    }

    ret = snd_usb_lock_shutdown(subs->stream->chip); //鎖定關機(斷開)任務並自動恢復
    if (ret < 0)
        goto stop_pipeline;

    ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D0);  //設置電源狀態
    if (ret < 0)
        goto unlock;

    ret = set_format(subs, fmt); //找到匹配的格式並設置接口,里面會設置同步端點,也就是這是同步傳輸
    if (ret < 0)
        goto unlock;

    subs->interface = fmt->iface;
    subs->altset_idx = fmt->altset_idx;
    subs->need_setup_ep = true;
}

3.預備傳輸
應用調用ioctl: ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE)  //主要是傳輸前的准備
對於UAC設備通過一系列掉用就會到snd_usb_pcm_prepare,
static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
{
    ret = set_format(subs, subs->cur_audiofmt); //設置,前面也有這個操作
    if (ret < 0)
        goto unlock;

    if (subs->need_setup_ep) {
        iface = usb_ifnum_to_if(subs->dev, subs->cur_audiofmt->iface);
        alts = &iface->altsetting[subs->cur_audiofmt->altset_idx];
        ret = snd_usb_init_sample_rate(subs->stream->chip,
                           subs->cur_audiofmt->iface,
                           alts,
                           subs->cur_audiofmt,
                           subs->cur_rate); //初始化采樣率
        if (ret < 0)
            goto unlock;

        ret = configure_endpoint(subs); //設置端點,報錯數據和同步端點
        if (ret < 0)
            goto unlock;
        subs->need_setup_ep = false;
    }

    /* some unit conversions in runtime */
    subs->data_endpoint->maxframesize =
        bytes_to_frames(runtime, subs->data_endpoint->maxpacksize);
    subs->data_endpoint->curframesize =
        bytes_to_frames(runtime, subs->data_endpoint->curpacksize);

    /* reset the pointer */
    subs->hwptr_done = 0;
    subs->transfer_done = 0;
    subs->last_delay = 0;
    subs->last_frame_number = 0;
    runtime->delay = 0;

    /* for playback, submit the URBs now; otherwise, the first hwptr_done
     * updates for all URBs would happen at the same time when starting */
    if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) //如果是播放,現在就提交URBs
        ret = start_endpoints(subs);
}
configure_endpoint里面會調用snd_usb_endpoint_set_params設置一個snd_usb_endpoint。數據端點用於發送音頻數據,同步端點用於接收設備端的反饋。
int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep, prepare_playback_urb
                snd_pcm_format_t pcm_format,
                unsigned int channels,
                unsigned int period_bytes,
                unsigned int period_frames,
                unsigned int buffer_periods,
                unsigned int rate,
                struct audioformat *fmt,
                struct snd_usb_endpoint *sync_ep)
{
    if (snd_usb_get_speed(ep->chip->dev) == USB_SPEED_FULL)
        ep->freqn = get_usb_full_speed_rate(rate);
    else
        ep->freqn = get_usb_high_speed_rate(rate);

    /* calculate the frequency in 16.16 format */
    ep->freqm = ep->freqn;
    ep->freqshift = INT_MIN;
    ep->phase = 0;
    switch (ep->type) {
    case  SND_USB_ENDPOINT_TYPE_DATA:
        err = data_ep_set_params(ep, pcm_format, channels,
                     period_bytes, period_frames,
                     buffer_periods, fmt, sync_ep); //如果是數據端點,使用data_ep_set_params
        break;
    case  SND_USB_ENDPOINT_TYPE_SYNC:
        err = data_ep_set_params(ep); //如果是同步端點,data_ep_set_params
        break;
    default:
        err = -EINVAL;
    }
    usb_audio_dbg(ep->chip,
        "Setting params for ep #%x (type %d, %d urbs), ret=%d\n",
        ep->ep_num, ep->type, ep->nurbs, err);

data_ep_set_params
data_ep_set_params里面會分配URB,傳輸完成后調用snd_complete_urb。 因為等時 urb 沒有初始化函數,必須手動初始化,所以下面是包括分配和初始化了。
static int data_ep_set_params(struct snd_usb_endpoint *ep,
                  snd_pcm_format_t pcm_format,
                  unsigned int channels,
                  unsigned int period_bytes,
                  unsigned int frames_per_period,
                  unsigned int periods_per_buffer,
                  struct audioformat *fmt,
                  struct snd_usb_endpoint *sync_ep)
{
     .......前面是一系列的計算URB.....

    /* allocate and initialize data urbs */
    for (i = 0; i < ep->nurbs; i++) {
        struct snd_urb_ctx *u = &ep->urb[i];
        u->index = i;
        u->ep = ep;
        u->packets = urb_packs;
        u->buffer_size = maxsize * u->packets;

        if (fmt->fmt_type == UAC_FORMAT_TYPE_II)
            u->packets++; /* for transfer delimiter */
        u->urb = usb_alloc_urb(u->packets, GFP_KERNEL); //分配URB
        if (!u->urb)
            goto out_of_memory;
        u->urb->transfer_buffer =
            usb_alloc_coherent(ep->chip->dev, u->buffer_size,
                       GFP_KERNEL, &u->urb->transfer_dma);
        if (!u->urb->transfer_buffer)
            goto out_of_memory;
        u->urb->pipe = ep->pipe;
        u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
        u->urb->interval = 1 << ep->datainterval;
        u->urb->context = u;
        u->urb->complete = snd_complete_urb; //傳輸完成后調用
        INIT_LIST_HEAD(&u->ready_list);
    }
sync_ep_set_params
里面會分配URB,傳輸完成后調用snd_complete_urb。因為等時 urb 沒有初始化函數,必須手動初始化,所以下面是包括分配和初始化了。
static int sync_ep_set_params(struct snd_usb_endpoint *ep)
{
    int i;

    ep->syncbuf = usb_alloc_coherent(ep->chip->dev, SYNC_URBS * 4,
                     GFP_KERNEL, &ep->sync_dma);
    if (!ep->syncbuf)
        return -ENOMEM;

    for (i = 0; i < SYNC_URBS; i++) {
        struct snd_urb_ctx *u = &ep->urb[i];
        u->index = i;
        u->ep = ep;
        u->packets = 1;
        u->urb = usb_alloc_urb(1, GFP_KERNEL); 分配URB
        if (!u->urb)
            goto out_of_memory;
        u->urb->transfer_buffer = ep->syncbuf + i * 4;
        u->urb->transfer_dma = ep->sync_dma + i * 4;
        u->urb->transfer_buffer_length = 4;
        u->urb->pipe = ep->pipe;
        u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
        u->urb->number_of_packets = 1;
        u->urb->interval = 1 << ep->syncinterval;
        u->urb->context = u;
        u->urb->complete = snd_complete_urb;
    }
    ep->nurbs = SYNC_URBS;
    return 0;
}
start_endpoints調用snd_usb_endpoint_start。
int snd_usb_endpoint_start(struct snd_usb_endpoint *ep)
{
    /*
     * If this endpoint has a data endpoint as implicit feedback source,
     * don't start the urbs here. Instead, mark them all as available,
     * wait for the record urbs to return and queue the playback urbs
     * from that context.
     */
    set_bit(EP_FLAG_RUNNING, &ep->flags);
    if (snd_usb_endpoint_implicit_feedback_sink(ep)) { //如果這個端點有一個數據端點是作為隱含的反饋源,不在這里啟動urb.
        for (i = 0; i < ep->nurbs; i++) {
            struct snd_urb_ctx *ctx = ep->urb + i;
            list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs);
        }
        return 0;
    }

    for (i = 0; i < ep->nurbs; i++) {
        struct urb *urb = ep->urb[i].urb;

        if (snd_BUG_ON(!urb))
            goto __error;

        if (usb_pipeout(ep->pipe)) {
            prepare_outbound_urb(ep, urb->context); //調用prepare_playback_urb准備urb,主要是緩沖區的操作
        } else {
            prepare_inbound_urb(ep, urb->context); 
        }

        err = usb_submit_urb(urb, GFP_ATOMIC); //提交URB,成功后會調用snd_complete_urb
        if (err < 0) {
            usb_audio_err(ep->chip,
                "cannot submit urb %d, error %d: %s\n",
                i, err, usb_error_string(err));
            goto __error;
        }
        set_bit(i, &ep->active_mask);
    }
    return 0;

4.寫入數據
應用調用ioctl:ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)
pcm_write寫下來的數據都是放在substream->runtime->dma_area里面。
對於UAC設備通過一系列掉用就會到snd_usb_substream_playback_trigger啟動傳輸。前面准備階段已經提交了URB,這里同時執行SNDRV_PCM_TRIGGER_START和SNDRV_PCM_TRIGGER_PAUSE_RELEASE兩個分支。
static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substream,  int cmd)
{
    struct snd_usb_substream *subs = substream->runtime->private_data;

    switch (cmd) {
    case SNDRV_PCM_TRIGGER_START:
        subs->trigger_tstamp_pending_update = true; //啟動只更新標志位
        /* fall through */
    case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:  //注意,前面的case沒有return語句,所以會下來執行這個
        subs->data_endpoint->prepare_data_urb = prepare_playback_urb;
        subs->data_endpoint->retire_data_urb = retire_playback_urb;
        subs->running = 1;
        return 0;
    case SNDRV_PCM_TRIGGER_STOP: //停止
        stop_endpoints(subs, false);
        subs->running = 0;
        return 0;
    case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
        subs->data_endpoint->prepare_data_urb = NULL;
        /* keep retire_data_urb for delay calculation */
        subs->data_endpoint->retire_data_urb = retire_playback_urb;
        subs->running = 0;
        return 0;
    }
    return -EINVAL;
}
為什么這里就能啟動播放能呢。准備階段數據URB發送的是靜音數據,啟動之后才發送正在的數據。
static void prepare_outbound_urb(struct snd_usb_endpoint *ep,
                 struct snd_urb_ctx *ctx)
{
    switch (ep->type) {
    case SND_USB_ENDPOINT_TYPE_DATA: 
        if (ep->prepare_data_urb) { //啟動之后,給ep->prepare_data_urb賦值之后,才准備真正的數據
            ep->prepare_data_urb(ep->data_subs, urb);
        } else { //這里是准備階段,只是發送靜音數據
            /* no data provider, so send silence */
            prepare_silent_urb(ep, ctx);
        }
        break;
    case SND_USB_ENDPOINT_TYPE_SYNC:
        if (snd_usb_get_speed(ep->chip->dev) >= USB_SPEED_HIGH) {
            /*
             * fill the length and offset of each urb descriptor.
             * the fixed 12.13 frequency is passed as 16.16 through the pipe.
             */
            urb->iso_frame_desc[0].length = 4;
            urb->iso_frame_desc[0].offset = 0;
            cp[0] = ep->freqn;
            cp[1] = ep->freqn >> 8;
            cp[2] = ep->freqn >> 16;
            cp[3] = ep->freqn >> 24;
        } else {
            /*
             * fill the length and offset of each urb descriptor.
             * the fixed 10.14 frequency is passed through the pipe.
             */
            urb->iso_frame_desc[0].length = 3;
            urb->iso_frame_desc[0].offset = 0;
            cp[0] = ep->freqn >> 2;
            cp[1] = ep->freqn >> 10;
            cp[2] = ep->freqn >> 18;
        }
        break;
    }
}

5.傳輸回調
傳輸成功后會調用snd_complete_urb。
static void snd_complete_urb(struct urb *urb)
{
    if (usb_pipeout(ep->pipe)) { //如果是輸出通道,這里是處理播放的情況
        retire_outbound_urb(ep, ctx); //調用retire_playback_urb,數據播放完成后進行處理,降低延遲
        /* can be stopped during retire callback */
        if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags)))
            goto exit_clear;

        if (snd_usb_endpoint_implicit_feedback_sink(ep)) {
            spin_lock_irqsave(&ep->lock, flags);
            list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs);
            spin_unlock_irqrestore(&ep->lock, flags);
            queue_pending_output_urbs(ep);
            goto exit_clear;
        }
        prepare_outbound_urb(ep, ctx); //准備一個播放urb提交到總線。
        /* can be stopped during prepare callback */
        if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags)))
            goto exit_clear;
    } else { //如果是輸入通道,這里是處理錄音的情況
        retire_inbound_urb(ep, ctx); //如果是同步端點,使用snd_usb_handle_sync_urb解析usb同步包;如果不是,使用retire_capture_urb,雙緩存避免溢出
        /* can be stopped during retire callback */
        if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags)))
            goto exit_clear;
        prepare_inbound_urb(ep, ctx); //准備一個錄音或同步urb以提交到總線
    }

    err = usb_submit_urb(urb, GFP_ATOMIC); //提交urb
    if (err == 0)
        return;
}

參考:




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM