rk音頻驅動分析之tinyplay播放


一.tinyplay播放
操作命令:tinyplay /sdcard/test.wav
Tinyplay.c (external\tinyalsa) 
    file = fopen(filename, "rb");  //對應的音頻文件
    fread(&riff_wave_header, sizeof(riff_wave_header), 1, file); //讀取音頻文件頭部
     /* parse command line arguments */
    處理一些參數的問題
    //參數分別是播放的文件,聲卡,通道,采樣率,位數,DMA一次傳輸的數據量,傳輸次數
    play_sample(file, card, device, chunk_fmt.num_channels, chunk_fmt.sample_rate,
                chunk_fmt.bits_per_sample, period_size, period_count);
        struct pcm_config config;  //用來記錄音頻信息
        config.channels = channels;
        config.rate = rate;
        config.period_size = period_size;
        config.period_count = period_count;
        if (bits == 32) //格式
            config.format = PCM_FORMAT_S32_LE;
        else if (bits == 16)
            config.format = PCM_FORMAT_S16_LE;
        if (!sample_is_playable(card, device, channels, rate, bits, period_size, period_count))
            struct pcm_params *params;
            params = pcm_params_get(card, device, PCM_OUT);
            //我們這里是/dev/snd/pcmC0D0p
            snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, flags & PCM_IN ? 'c' : 'p');
            fd = open(fn, O_RDWR); //打開播放dev,驅動分析一
            //分配參數結構體
            params = calloc(1, sizeof(struct snd_pcm_hw_params));
            param_init(params); //初始化
            ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params) //會調用到驅動,獲取參數,驅動分析二
    pcm = pcm_open(card, device, PCM_OUT, &config);
        pcm->config = *config;
        snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, flags & PCM_IN ? 'c' : 'p');
        pcm->flags = flags;
        pcm->fd = open(fn, O_RDWR); //打開/dev/snd/pcmC0D0p節點,已經分析了
        ////獲取pcm的信息,包括card,流,dma各種信息
        ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info) //獲取PCM信息,驅動分析三
        param_init(&params); 初始化參數
        //包括format,channels等
        param_set_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT,pcm_format_to_alsa(config->format));
        。。。。。。很多設置。。。。。
        ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params) //驅動分析四
         /* get our refined hw_params */
        pcm->buffer_size = config->period_count * config->period_size; //緩沖區buf大小
        if (flags & PCM_MMAP) //如果支持mmap
            //mmap將一個文件或者其它對象映射進內存,調用底層的mmap函數,驅動分析五
                                                          起始地址                    長度
            pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
                                 表示頁可以讀寫,與其它進程共享映射空間                        有效的文件描述詞
                                PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0);
        params.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;
        sparams.period_step = 1;
        //這種開始和定制閾值
        pcm->config.start_threshold = sparams.start_threshold = config->period_count * config->period_size / 2;
        pcm->config.stop_threshold = sparams.stop_threshold = config->period_count * config->period_size;
        if (pcm->flags & PCM_MMAP)
             pcm->config.avail_min = sparams.avail_min = pcm->config.period_size;
        sparams.xfer_align = config->period_size / 2; /* needed for old kernels */
        ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams) ////主要是把上層的參數賦值給snd_pcm_runtime,驅動分析六
       rc = pcm_hw_mmap_status(pcm); //單獨分析
        #ifdef SNDRV_PCM_IOCTL_TTSTAMP
        int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;
        rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg); //設置為絕對時間
        #endif
        size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));  
            //frames * pcm->config.channels * byte
            return frames * pcm->config.channels *(pcm_format_to_bits(pcm->config.format) >> 3);
        buffer = malloc(size); //分配內存
         do {
            num_read = fread(buffer, 1, size, file); //把文件的內容讀到buffer里
            if (num_read > 0)
                if (pcm_write(pcm, buffer, num_read)) //然后寫入pcm,單獨分析
        } while (!close && num_read > 0);

1.static int pcm_hw_mmap_status(struct pcm *pcm)
    pcm->mmap_status = mmap(NULL, page_size, PROT_READ, MAP_FILE | MAP_SHARED, pcm->fd, SNDRV_PCM_MMAP_OFFSET_STATUS); //見驅動分析五,就是SNDRV_PCM_MMAP_OFFSET_STATUS這個不一樣
     if (pcm->flags & PCM_MMAP) //最少的可以用的大小
        pcm->mmap_control->avail_min = pcm->config.avail_min;
    
2.pcm_write(pcm, buffer, num_read)
    x.buf = (void*)data;
    x.frames = count / (pcm->config.channels * pcm_format_to_bits(pcm->config.format) / 8); //得到多少幀
    for (;;)
        if (!pcm->running)
            int prepare_error = pcm_prepare(pcm);
                ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) //調用驅動,驅動分析七
                pcm->prepared = 1;
            if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) //調用驅動,驅動分析八


二.驅動分析一
Pcm_native.c (kernel\sound\core) 
const 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,
 }
};

1.打開播放dev,驅動分析1
在_snd_pcm_new函數中注冊snd_pcm_dev_register函數,后面snd_device_register_all中統一調用dev->ops->dev_register
snd_pcm_dev_register函數中會注冊/dev/snd/pcmC0D0p和/dev/snd/pcmC0D0c,並提供操作函數,我們這里分析open

static int snd_pcm_playback_open(struct inode *inode, struct file *file)
    //通過次設備號和SNDRV_DEVICE_TYPE_PCM_PLAYBACK獲取snd_pcm指針
    pcm = snd_lookup_minor_data(iminor(inode),  SNDRV_DEVICE_TYPE_PCM_PLAYBACK); 
    err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
        //This function adds the file to the file linked-list of the card. This linked-list is used to keep tracking the connection state,
        // and to avoid the release of busy resources by hotplug.
        //這個函數把file 加入到cardlinked-list ,這個linked-list 是用來持續追蹤鏈接狀態的,可以避免很忙的資源被hotplug釋放
        err = snd_card_file_add(pcm->card, file);
            struct snd_monitor_file *mfile;
            mfile = kmalloc(sizeof(*mfile), GFP_KERNEL);
            mfile->file = file;
            list_add(&mfile->list, &card->files_list);  //加入card->files_list鏈表
        try_module_get(pcm->card->module)
        init_waitqueue_entry(&wait, current); //初始化等待隊列,當前的進程
        add_wait_queue(&pcm->open_wait, &wait); //把等待隊列加入到pcm->open_wait里面
        while (1)
            err = snd_pcm_open_file(file, pcm, stream);
                err = snd_pcm_open_substream(pcm, stream, file, &substream); //主要是初始化snd_pcm_runtime相關的東西,單獨分析2
                pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL); //分配snd_pcm_file
                pcm_file->substream = substream; 
                if (substream->ref_count == 1) //因為snd_pcm_open_substream已經初始化,這里是1
                    substream->file = pcm_file;
  substream->pcm_release = pcm_release_private;
file->private_data = pcm_file;
set_current_state(TASK_INTERRUPTIBLE);  //設置當前進程可被中斷喚醒
schedule(); //放棄cpu
if (signal_pending(current))   //檢查當前進程是否有信號處理,返回不為0表示有信號需要處理。
        remove_wait_queue(&pcm->open_wait, &wait);
    

                 
2.//單獨分析2
 err = snd_pcm_open_substream(pcm, stream, file, &substream);
    struct snd_pcm_substream *substream;
    err = snd_pcm_attach_substream(pcm, stream, file, &substream);
        struct snd_pcm_str * pstr;
        pstr = &pcm->streams[stream]; //這里是SNDRV_PCM_STREAM_PLAYBACK,找到對應的snd_pcm_str 
        card = pcm->card;
        list_for_each_entry(kctl, &card->ctl_files, list) //遍歷snd_ctl_file
            if (kctl->pid == task_pid(current)) //找到與當前進程pid一樣的snd_ctl_file
                prefer_subdevice = kctl->prefer_pcm_subdevice;
        switch (stream)
        case SNDRV_PCM_STREAM_PLAYBACK:
            if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) //如果是半雙工
                for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; substream; substream = substream->next)
                    if (SUBSTREAM_BUSY(substream)) //如果正常忙,就是正在工作,就返回
        case SNDRV_PCM_STREAM_CAPTURE: //錄音和播放一樣,有在工作的就返回
        。。。。。中間有很多判斷設備是不是在忙的代碼。。。。。。。
        runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); //分配snd_pcm_runtime結構體
        size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)); //環形緩沖區結構體大小,需要字節對齊
        runtime->status = snd_malloc_pages(size, GFP_KERNEL); //分配頁
        size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control)); //映射控制
        runtime->control = snd_malloc_pages(size, GFP_KERNEL);
        init_waitqueue_head(&runtime->sleep); //初始化睡眠等待隊列
        init_waitqueue_head(&runtime->tsleep);
        runtime->status->state = SNDRV_PCM_STATE_OPEN; //狀態為開啟
        substream->runtime = runtime; //賦值給substream
        substream->private_data = pcm->private_data;
        substream->ref_count = 1;
       substream->pid = get_pid(task_pid(current));
       pstr->substream_opened++;
       *rsubstream = substream;
    //給pcm加一些限制,// 為該runtime追加hw硬件rules規則,來最終約束substream所屬聲卡支持的參數特性
    err = snd_pcm_hw_constraints_init(substream); //設置一些限制,比較繁瑣,暫不分析
    err = substream->ops->open(substream) //調用到pcm的open,主要是初始化code,platform,dai等,單獨分析3
    substream->hw_opened = 1; //說明已經打開
    err = snd_pcm_hw_constraints_complete(substream); //主要設置一些限制,包括權限,格式,channels等
        err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_ACCESS, mask);
        err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
        hw->channels_min, hw->channels_max);       


3.pcm的open,單獨分析3
static int soc_pcm_open(struct snd_pcm_substream *substream)
    pm_runtime_get_sync(cpu_dai->dev);
    pm_runtime_get_sync(codec_dai->dev);
    pm_runtime_get_sync(platform->dev);
    /* startup the audio subsystem */
    if (cpu_dai->driver->ops->startup) //這里沒有,建立cpu dai鏈接
        ret = cpu_dai->driver->ops->startup(substream, cpu_dai);
    if (platform->driver->ops && platform->driver->ops->open)
        ret = platform->driver->ops->open(substream);  //打開platform,也就是設置DMA相關參數,單獨分析4
        //Soc-generic-dmaengine-pcm.c (kernel\sound\soc),調用dmaengine_pcm_open函數
    if (codec_dai->driver->ops->startup) //調用codec_dai的startup函數,只是設置了速度限制,單獨分析5
        ret = codec_dai->driver->ops->startup(substream, codec_dai); 
    if (rtd->dai_link->ops && rtd->dai_link->ops->startup) //dai_link的startup函數,暫時沒有
        ret = rtd->dai_link->ops->startup(substream);
    /* Check that the codec and cpu DAIs are compatible */
    //檢查codec和cpu DAIs會不會兼容,檢查的有rate,channels,formats,rates,
    if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)  //這里播放
        //取讀取速率的最大值,也就是codec和cpu DAIs都支持的值
        runtime->hw.rate_min = max(codec_dai_drv->playback.rate_min, cpu_dai_drv->playback.rate_min);
        runtime->hw.rate_max = min(codec_dai_drv->playback.rate_max, cpu_dai_drv->playback.rate_max);
        。。。。。。。。
    else //錄音,與播放大致一樣的檢查步驟
    snd_pcm_limit_hw_rates(runtime); //determine rate_min/rate_max fields,設置最大最小速度
    soc_pcm_apply_msb(substream, codec_dai); //設置多少位的限制,codec_dai和cpu_dai一樣
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
            bits = dai->driver->playback.sig_bits; //內容的比特數
            for (i = 0; i < ARRAY_SIZE(sample_sizes); i++)  //這里判斷是24位的還是32位
                if (bits >= sample_sizes[i])
                    continue
                //設置限制
                ret = snd_pcm_hw_constraint_msbits(substream->runtime, 0, sample_sizes[i], bits);
    soc_pcm_apply_msb(substream, cpu_dai);
    /* Symmetry only applies if we've already got an active stream. */
    //只有當我們已經有了一個活動流時才會出現對稱性
    if (cpu_dai->active)  //設置對稱性
        ret = soc_pcm_apply_symmetry(substream, cpu_dai);
    if (codec_dai->active) {
        ret = soc_pcm_apply_symmetry(substream, codec_dai);

            

4.打開platform,也就是DMA相關的東西,單獨分析4
platform相關的參數
static const struct snd_pcm_hardware rockchip_pcm_hardware = {
 .info = SNDRV_PCM_INFO_INTERLEAVED |
        SNDRV_PCM_INFO_BLOCK_TRANSFER |
        SNDRV_PCM_INFO_MMAP |
        SNDRV_PCM_INFO_MMAP_VALID |
        SNDRV_PCM_INFO_PAUSE |
        SNDRV_PCM_INFO_RESUME,
 .formats = SNDRV_PCM_FMTBIT_S24_LE |
        SNDRV_PCM_FMTBIT_S20_3LE |
        SNDRV_PCM_FMTBIT_S16_LE |
        SNDRV_PCM_FMTBIT_S32_LE ,
 .channels_min = 2,
 .channels_max = 8,
 .buffer_bytes_max = 2*1024*1024,/*128*1024,*/
 .period_bytes_min = 64,
 .period_bytes_max = 512*1024,/*32*1024,//2048*4,///PAGE_SIZE*2,*/
 .periods_min = 3,
 .periods_max = 128,
 .fifo_size = 16,
};
static int dmaengine_pcm_open(struct snd_pcm_substream *substream)
    //首先注冊上敘的參數,這個在注冊的platform的會賦值rockchip_pcm_hardware 結構體
    ret = snd_soc_set_runtime_hwparams(substream, pcm->config->pcm_hardware);
        struct snd_pcm_runtime *runtime = substream->runtime;
        runtime->hw.info = hw->info;
        runtime->hw.formats = hw->formats;
        runtime->hw.period_bytes_min = hw->period_bytes_min;
        runtime->hw.period_bytes_max = hw->period_bytes_max;
        runtime->hw.periods_min = hw->periods_min;
        runtime->hw.periods_max = hw->periods_max;
        runtime->hw.buffer_bytes_max = hw->buffer_bytes_max;
        runtime->hw.fifo_size = hw->fifo_size;
    return snd_dmaengine_pcm_open(substream, chan);
        struct dmaengine_pcm_runtime_data *prtd;
        //設置限制參數,暫不分析
        ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS);
        prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); //分配dmaengine_pcm_runtime_data,用於記錄DMA數據
        prtd->dma_chan = chan;
        substream->runtime->private_data = prtd; //把數據賦值給runtime的private_data指針


5.調用codec_dai的startup函數,單獨分析5
    Es8323.c (kernel\sound\soc\codecs)
static int es8323_pcm_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
    //設置約束,主要是大約的速度,暫不具體分析
    snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE,  es8323->sysclk_constraints);
        return snd_pcm_hw_rule_add(runtime, cond, var, snd_pcm_hw_rule_list, (void *)l, var, -1);


三.驅動分析二
snd_pcm_common_ioctl1
    case SNDRV_PCM_IOCTL_HW_REFINE:
        return snd_pcm_hw_refine_user(substream, arg);
            params = memdup_user(_params, sizeof(*params)); //用戶態到內核態的拷貝
            err = snd_pcm_hw_refine(substream, params); //根據底層的限制,重新給params 賦值
                struct snd_pcm_hw_constraints *constrs = &substream->runtime->hw_constraints;
                m = hw_param_mask(params, k);
            copy_to_user(_params, params, sizeof(*params)) //拷貝給用戶空間
            


四.驅動分析三
static int snd_pcm_common_ioctl1(struct file *file, struct snd_pcm_substream *substream, unsigned int cmd, void __user *arg)
    case SNDRV_PCM_IOCTL_INFO:
        return snd_pcm_info_user(substream, arg); //獲取pcm的信息,包括card,流,dma各種信息
            info = kmalloc(sizeof(*info), GFP_KERNEL); //分配snd_pcm_info
            info->card = pcm->card->number;
   info->device = pcm->device;
            info->stream = substream->stream;
            info->subdevice = substream->number;
            strlcpy(info->id, pcm->id, sizeof(info->id));
            strlcpy(info->name, pcm->name, sizeof(info->name));
            info->dev_class = pcm->dev_class;
            info->dev_subclass = pcm->dev_subclass;
            info->subdevices_count = pstr->substream_count;
            info->subdevices_avail = pstr->substream_count - pstr->substream_opened;
            strlcpy(info->subname, substream->name, sizeof(info->subname));
            runtime = substream->runtime;
        if (runtime) { //這里沒有什么用
            info->sync = runtime->sync;
            substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_INFO, info);
        copy_to_user(_info, info, sizeof(*info)) //賦值給用戶空間
            

五.驅動分析四
case SNDRV_PCM_IOCTL_HW_PARAMS:
return snd_pcm_hw_params_user(substream, arg);
    params = memdup_user(_params, sizeof(*params)); //從用戶空間獲取
    err = snd_pcm_hw_params(substream, params);
        runtime = substream->runtime;
        err = snd_pcm_hw_refine(substream, params); //已經分析了,根據底層的限制,重新給params 賦值
        //choose a configuration defined by @params
        err = snd_pcm_hw_params_choose(substream, params);
        //對於所有的參數,包括FORMAT,CHANNELS,根據底層都返回最小值
        for (v = vars; *v != -1; v++) //    refine config space and return minimum value
            err = snd_pcm_hw_param_first(pcm, params, *v, NULL);
    if (substream->ops->hw_params != NULL) 
        //這里只有是設置cpu dai和codec dai的類型和時鍾等
        err = substream->ops->hw_params(substream, params); //調用到rk音頻驅動分析之machine的soc_pcm_hw_params函數
    copy_to_user(_params, params, sizeof(*params)) //拷貝給用戶空間



六.驅動分析五
DMA內存分配
static const struct vm_operations_struct snd_pcm_vm_ops_data_fault = {
 .open = snd_pcm_mmap_data_open,
 .close = snd_pcm_mmap_data_close,
 .fault = snd_pcm_mmap_data_fault,
};
static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area)
    pcm_file = file->private_data;
    substream = pcm_file->substream;
    offset = area->vm_pgoff << PAGE_SHIFT;
    switch (offset) //我們這里應該是snd_pcm_mmap_data
    case SNDRV_PCM_MMAP_OFFSET_STATUS:   
        if (pcm_file->no_compat_mmap)
            return snd_pcm_mmap_status(substream, file, area); //只是加了一個操作函數
                size = area->vm_end - area->vm_start;
                area->vm_ops = &snd_pcm_vm_ops_status;
                area->vm_private_data = substream;
                area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
    case SNDRV_PCM_MMAP_OFFSET_CONTROL:
        return snd_pcm_mmap_control(substream, file, area);
    default: 
        return snd_pcm_mmap_data(substream, file, area);
            //mmap DMA buffer,分配DMA內存
            size = area->vm_end - area->vm_start;
            offset = area->vm_pgoff << PAGE_SHIFT;
            dma_bytes = PAGE_ALIGN(runtime->dma_bytes); //需要字節對齊
            area->vm_ops = &snd_pcm_vm_ops_data; //操作函數
            area->vm_private_data = substream; //是substream指針
            if (substream->ops->mmap) //調用platform->driver->ops->mmap;,這個有在soc_new_pcm賦值
                err = substream->ops->mmap(substream, area); //這里沒有
            else
                err = snd_pcm_lib_default_mmap(substream, area); //mmap the DMA buffer on RAM
                    area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
                    //分配DMA緩沖區
                    return dma_mmap_coherent(substream->dma_buffer.dev.dev, area, substream->runtime->dma_area,
                    substream->runtime->dma_addr, area->vm_end - area->vm_start);
                    /* mmap with fault handler */
                    area->vm_ops = &snd_pcm_vm_ops_data_fault; //操作函數


七.驅動分析六
//主要是把上層的參數賦值給snd_pcm_runtime
static int snd_pcm_sw_params_user(struct snd_pcm_substream *substream, struct snd_pcm_sw_params __user * _params)
    copy_from_user(&params, _params, sizeof(params))
    err = snd_pcm_sw_params(substream, &params);
        struct snd_pcm_runtime *runtime;
        .......一些判斷......
        runtime->tstamp_mode = params->tstamp_mode; //給runtime相關賦值
        runtime->period_step = params->period_step;
        runtime->control->avail_min = params->avail_min;
        runtime->start_threshold = params->start_threshold;
        runtime->stop_threshold = params->stop_threshold;
        runtime->silence_threshold = params->silence_threshold;
        runtime->silence_size = params->silence_size;
        params->boundary = runtime->boundary;
        if (snd_pcm_running(substream)) //如果正在運行
            //fill ring buffer with silence,用安靜填滿環形緩沖區
            snd_pcm_playback_silence(substream, ULONG_MAX);
            err = snd_pcm_update_state(substream, runtime); //更新狀態
    copy_to_user(_params, &params, sizeof(params))


八.驅動分析七
//prepare the PCM substream to be triggerable,預備pcm可以觸發
static struct action_ops snd_pcm_action_prepare = {
 .pre_action = snd_pcm_pre_prepare,
 .do_action = snd_pcm_do_prepare,
 .post_action = snd_pcm_post_prepare
};
static int snd_pcm_prepare(struct snd_pcm_substream *substream, struct file *file)
    // wait until the power-state is changed.等待電源標志變化
    if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0))
        init_waitqueue_entry(&wait, current); //這個函數的作用是用新進程來初始化隊列
        add_wait_queue(&card->power_sleep, &wait); //把等待元素wait,加到 card->power_slee隊列前面
        while (1)
            set_current_state(TASK_UNINTERRUPTIBLE);
            schedule_timeout(30 * HZ);
    res = snd_pcm_action_nonatomic(&snd_pcm_action_prepare, substream, f_flags);
        if (snd_pcm_stream_linked(substream)) //查看有沒有連接其他的substream
            res = snd_pcm_action_group(ops, substream, state, 0);  //暫不分析
        else
            res = snd_pcm_action_single(ops, substream, state);
                res = ops->pre_action(substream, state); //調用snd_pcm_action_prepare ,前面有賦值
                    //we use the second argument for updating f_flags,這里只是更新f_flags 
                    //這些是文件標志, 例如 O_RDONLY, O_NONBLOCK,驅動應當檢查O_NONBLOCK 標志來看是否是請求非阻塞操作
                    substream->f_flags = f_flags; 
                res = ops->do_action(substream, state);
                    //主要是通知widget更新電源狀態,然后設置聲卡打開播放
                    err = substream->ops->prepare(substream); //初始化賦值soc_pcm_prepare,單獨分析
                    return snd_pcm_do_reset(substream, 0);
                        int err = substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL);
                            if (platform->driver->ops->ioctl)
                                return platform->driver->ops->ioctl(substream, cmd, arg); //這里調用rk音頻驅動之platform的snd_pcm_lib_ioctl
                            return snd_pcm_lib_ioctl(substream, cmd, arg); //這里不會到這里
                        runtime->hw_ptr_base = 0;
                        .....環形緩沖區指針狀態相關的reset...
                ops->post_action(substream, state);  //初始化賦值snd_pcm_post_prepare
                    runtime->control->appl_ptr = runtime->status->hw_ptr;
                    snd_pcm_set_state(substream, SNDRV_PCM_STATE_PREPARED); //更新狀態/為* stream is ready to start */



1.soc_pcm_prepare
/*
 * Called by ALSA when the PCM substream is prepared, can set format, sample
 * rate, etc. This function is non atomic and can be called multiple times,
 * it can refer to the runtime info.
 */
    if (rtd->dai_link->ops && rtd->dai_link->ops->prepare)
        ret = rtd->dai_link->ops->prepare(substream); //我們這里沒有
    if (platform->driver->ops && platform->driver->ops->prepare) //這里沒有
        ret = codec_dai->driver->ops->prepare(substream, codec_dai);
    if (cpu_dai->driver->ops->prepare) //這里沒有
        ret = cpu_dai->driver->ops->prepare(substream, cpu_dai);
    //Sends a stream event to the dapm core. The core then makes any necessary widget power changes.
    //這里會調動到widget 
    snd_soc_dapm_stream_event(rtd, substream->stream, SND_SOC_DAPM_STREAM_START);
        soc_dapm_stream_event(rtd, stream, event);
        if (stream == SNDRV_PCM_STREAM_PLAYBACK)
            w_cpu = cpu_dai->playback_widget;
            w_codec = codec_dai->playback_widget;
        else
            ......
        if (w_cpu)
            dapm_mark_dirty(w_cpu, "stream event");  //加入dapm_dirty鏈表
                list_add_tail(&w->dirty, &w->dapm->card->dapm_dirty);
        if (w_codec)
            dapm_mark_dirty(w_codec, "stream event");
        //Scan each dapm widget for complete audio path,這里就是搜索path
        dapm_power_widgets(&rtd->card->dapm, event);        
    snd_soc_dai_digital_mute(codec_dai, 0, substream->stream);
        //Mutes the DAI DAC.
        if (dai->driver->ops->mute_stream)
            return dai->driver->ops->mute_stream(dai, mute, direction); //我們這里沒有
        else if (direction == SNDRV_PCM_STREAM_PLAYBACK && dai->driver->ops->digital_mute)
            return dai->driver->ops->digital_mute(dai, mute); //調用rk音頻驅動分析之codec里面的es8323_mute,進行播放准備

                   
九.驅動分析八
snd_pcm_playback_ioctl里面的snd_pcm_playback_ioctl1的
static int snd_pcm_playback_ioctl1(struct file *file, struct snd_pcm_substream *substream, unsigned int cmd, void __user *arg)
    switch (cmd) 
        case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
            if (put_user(0, &_xferi->result)) //給應用發送0的返回值
            copy_from_user(&xferi, _xferi, sizeof(xferi)) //從用戶拷貝數據
            result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
                /* sanity-check for read/write methods */
                err = pcm_sanity_check(substream); //檢查有不有讀寫函數
                runtime = substream->runtime;
                nonblock = !!(substream->f_flags & O_NONBLOCK);
                //傳輸函數,單獨分析, snd_pcm_lib_write_transfer這個也要單獨分析
                return snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock, snd_pcm_lib_write_transfer);

1.snd_pcm_lib_write1
    runtime->twake = runtime->control->avail_min ? : 1;
    if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) //如果正在進行
        snd_pcm_update_hw_ptr(substream); ///* CAUTION: call it with irq disabled */
            return snd_pcm_update_hw_ptr0(substream, 0); //暫不分析
    avail = snd_pcm_playback_avail(runtime); //獲取當前ring buf的可用大小
        // 讀指針+總的buf size-寫指針的數值
        
        //上圖白色部分avail ,灰色是已經用掉的
        snd_pcm_sframes_t avail = snd_pcm_sframes_t avail = runtime->status->hw_ptr + runtime->buffer_size - runtime->control->appl_ptr;;
        if (avail < 0) //讀寫指針換位的時候
            avail += runtime->boundary; // avail=runtime->boundary + avail;加負數,還是等於可以用的
        else if ((snd_pcm_uframes_t) avail >= runtime->boundary) //溢出了,無可用的,已經是負的
            avail -= runtime->boundary; //avail = runtime->boundary - avail ; //返回負的可以的大小
    while (size > 0)
        snd_pcm_uframes_t frames, appl_ptr, appl_ofs; //每塊buf
        snd_pcm_uframes_t cont;
        if (!avail) //無可用的緩沖區
            if (nonblock) //非阻塞模式,返回
                err = -EAGAIN;
            //如果非0,就喚醒,然后開始傳輸
             runtime->twake = min_t(snd_pcm_uframes_t, size, runtime->control->avail_min ? : 1);
            //Wait until avail_min data becomes available,等待avail_min 可以用
             err = wait_for_avail(substream, &avail);
        frames = size > avail ? avail : size; //得到frames 大小
        cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
        appl_ptr = runtime->control->appl_ptr; //HW BUF寫指針的位置
        appl_ofs = appl_ptr % runtime->buffer_size; //hw_ofs是讀指針在當前HW buffer中的位置。
        //傳輸,這個是傳進來的snd_pcm_lib_write_transfer函數指針,單獨分析2
        err = transfer(substream, appl_ofs, data, offset, frames);  //這里只是從用戶空間把數據拷貝到DMA
        appl_ptr += frames; //寫指針移位
        if (appl_ptr >= runtime->boundary) //如果已經越位
            appl_ptr -= runtime->boundary; //回到環形緩沖區頭部
        runtime->control->appl_ptr = appl_ptr;
        if (substream->ops->ack) //這里沒有
            substream->ops->ack(substream);
        offset += frames; //offset = offset + frames //偏移增加
        size -= frames; // size = size - frames;
        xfer += frames; //xfer = xfer + frames;
        avail -= frames; //avail = avail - frames;
        if (runtime->status->state == SNDRV_PCM_STATE_PREPARED &&  //之前已經准備好傳輸了
          snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) //可以讀的界限>可以傳輸的界限
            err = snd_pcm_start(substream); //開始傳輸,單獨分析3
        

2.單獨分析2:snd_pcm_lib_write_transfer
static int snd_pcm_lib_write_transfer(struct snd_pcm_substream *substream, unsigned int hwoff,
          unsigned long data, unsigned int off, snd_pcm_uframes_t frames)
    if (substream->ops->copy) //這里沒有
        err = substream->ops->copy(substream, -1, hwoff, buf, frames)  //這里會調用platform->driver->ops->copy;
            platform->driver->ops->copy; //沒有
    else
        char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
        copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames)) //從用戶空間拷貝


單獨分析3:snd_pcm_start
static struct action_ops snd_pcm_action_start = {
 .pre_action = snd_pcm_pre_start,
 .do_action = snd_pcm_do_start,
 .undo_action = snd_pcm_undo_start,
 .post_action = snd_pcm_post_start
};

int snd_pcm_start(struct snd_pcm_substream *substream)
    return snd_pcm_action(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
        res = snd_pcm_action_single(ops, substream, state);
            res = ops->pre_action(substream, state); //調用snd_pcm_pre_start
                //check whether any data exists on the playback buffer
                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&  !snd_pcm_playback_data(substream))
                    if (runtime->stop_threshold >= runtime->boundary)
                        return 1;
                    //如果可用的buf少於總的的buf,說明有數據,就是可以的
                    return snd_pcm_playback_avail(runtime) < runtime->buffer_size; 
                runtime->trigger_master = substream;
            res = ops->do_action(substream, state);   \\調用snd_pcm_do_start      
                return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);    //開始傳輸,單獨分析4
            ops->post_action(substream, state); \\調用snd_pcm_post_start
                snd_pcm_trigger_tstamp(substream); //獲取時間
                runtime->hw_ptr_jiffies = jiffies; //系統時間
                runtime->hw_ptr_buffer_jiffies = (runtime->buffer_size * HZ) / runtime->rate; //傳輸需要的時間
                runtime->status->state = state; //SNDRV_PCM_STATE_RUNNING
                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && runtime->silence_size > 0)
                    //when runtime->silence_size >= runtime->boundary - fill processed area with silence immediately
                    snd_pcm_playback_silence(substream, ULONG_MAX); //如果有silence_size ,就把他填到緩沖區里
                if (substream->timer)
                    snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTART,  &runtime->trigger_tstamp);
                
                


單獨分析4:soc_pcm_trigger
static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
    if (codec_dai->driver->ops->trigger) //這里沒有
        ret = codec_dai->driver->ops->trigger(substream, cmd, codec_dai);
    if (platform->driver->ops && platform->driver->ops->trigger) //進行DMA傳輸到I2S控制器
        ret = platform->driver->ops->trigger(substream, cmd); //rk音頻驅動之platform的snd_dmaengine_pcm_trigger
    if (cpu_dai->driver->ops->trigger) //調用rk音頻驅動分析之codec的rockchip_i2s_trigger
        ret = cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai); //從i2s寄存器到codec的傳輸
        

    


免責聲明!

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



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