赞
踩
1.ipu_soc结构体:
struct ipu_soc {
2.ipu_channel_t枚举:
typedef enum {
再来看看这个_MAKE_CHAN宏:
#define _MAKE_CHAN(num, v_in, g_in, a_in, out) \
从这里就可以看出来,ipu_channel_t只是根据5个值左移形成的一个数字。
那么想要从channel中获取它的ID号怎么办?只需要将它右移24位即可,就能得到,就是下面一个宏:
#define IPU_CHAN_ID(ch) (ch >> 24)
再来看看channel_2_dma函数:
static inline uint32_t channel_2_dma(ipu_channel_t ch, ipu_buffer_t type)
这个函数能够根据不同的type类型从channel里面提取出所使用的dmachannel。
typedef enum {
同时根据_MAKE_CHAN宏中几个变量的名字,就能够理解ipu_channel_t中各个位的含义:
0~5位:输出dmachannel号
6~11位:alpha通道号
12~17位:graph通道号
18~23位:video输入dmachannel号
24~ :channel的序列号
3. ipu_channel_params_t联合:
typedef union {
这个ipu_channel_params_t这个联合,在这个联合中对于每一种channel都有一个对应的结构体类型来保存channel的参数。如果想要获取哪一个channel的信息,就从这个联合中的对应channel里面找即可。同时,在initchannel的时候,也是通过对这个联合里面对应的channel参数赋值。
这个ipu_common.c函数提供ipu底层函数调用的一些关系和函数。
(一)分析这个文件从init函数入口,发现有这个subsys_initcall,说明ipu是作为一个子系统注册到内核中的:
(二)这个mxcipu_driver结构体注册到平台以后,如果有匹配的设备的话,就会调用其中的probe函
static int ipu_probe(struct platform_device *pdev)
/*以imx6qp为例(后面本文件中如果有涉及到板子的一些资源,都以imx6qp为例),
*iputype= of_id->data,这个of_id返回的是imx_ipuv3_dt_ids[]数组中的某一项,
static const struct of_device_id imx_ipuv3_dt_ids[] = {
在本文件中就是{.compatible = "fsl,imx6qp-ipu", .data = &ipu_type_imx6qp,}这一项。所以:
*iputype= &ipu_type_imx6qp,
*devtype= 下面标红的部分:
static struct ipu_platform_type ipu_type_imx6qp = {
*/
dev_dbg(&pdev->dev, "<%s>\n", __func__);
/*从pdev->dev.of_node这个devicenode结构体里面读取“bypass_reset”这一项,将读出的结果存在&bypass_reset中。*/
id = of_alias_get_id(pdev->dev.of_node, "ipu");
/*这个of_alias_get_id函数的大致意思是根据名字“ipu”获取到它的id,将这个id返回。但是有两个ipu呢,这个怎么选择??如果根据名字的话,怎么区分这两个ipu?*/
ipu = &ipu_array[id];
/*这一些就是根据上面获取到的信息来填充这个structipu_soc *ipu结构体。*/
dev_dbg(&pdev->dev, "revision is %s\n", devtype->name);
/*获取irq资源和内存资源。*/
if (!devm_request_mem_region(&pdev->dev, res->start,
/*申请I/O内存资源,申请后还需要通过ioremap等函数映射后才能够使用。*/
ret = devm_request_irq(&pdev->dev, ipu->irq_sync,
/*为上面获取到的irq资源注册中断服务函数。*/
ipu_base = res->start; //ipu地址的初始值。
/*根据获取到的资源来为每个寄存器映射内存空间。*/
ipu->ipu_clk = devm_clk_get(ipu->dev, "bus");
/*获取"bus"的时钟。*/
/* ipu_clk is always prepared */
/*这个函数是clk_prepare和clk_enable两个函数的集合,其中clk_prepare函数是个预定义的函数,需要定义CONFIG_HAVE_CLK_PREPARE这个宏,然后就是调用clk_enable函数来使能时钟。*/
ipu->prg_clk = devm_clk_get(ipu->dev, "prg");
/*获取"prg"的时钟。*/
ipu->online = true;
/*这个online是一个bool类型的变量,表示当前这个ipu是否正在使用中。*/
platform_set_drvdata(pdev, ipu);
/*设置私有数据。*/
/*下面这个bypass_reset参数在前面通过of_property_read_u32函数获得了,从dts文件中可以看到它等于0.同时在structipu_soc中关于这个bypass_reset有这样的注释:Bypassreset to avoid display channel being stopped by probe since it maystarts to work inbootloader.这个值是从dts文件中获得的bypass_reset,因为在开发板启动过程中,需要使能屏幕来显示,所以显示通道可能已将在bootloader中开启了,在这里设置这个值,使得显示通道在probe函数中不会关闭,也就是这个参数的含义(旁路)。*/
if (!bypass_reset) {
/*这个ipu_mem_reset函数同样在这个文件中,如下所示:
static int ipu_mem_reset(struct ipu_soc *ipu)
这个ipu_disp_init函数同样在ipu_common.c文件中,如下所示:
void ipu_disp_init(struct ipu_soc *ipu)
最后通过ipu_cm_write函数来设置有关显示的一些寄存器。
*/
/* setup ipu clk tree after ipu reset */
/*然后调用ipu_clk_setup_enable函数设置时钟树,这个函数也在这个ipu_common.c文件中,在后面分析。*/
if (devtype->idmac_used_bufs_present) {
/*上面这段代码先通过ipu_idmac_read函数来读取IDMAC_CONF寄存器的值,然后根据iputype的一些信息设置它们在寄存器中对应的值,最后将新值通过ipu_idmac_write函数重新写入寄存器中。这个函数用来配置IDMAC。*/
/* Set sync refresh channels and CSI->mem channel as high priority */
/*通过设置IDMAC_CHA_PRI(0)寄存器,将syncrefresh channels 和CSI->memchannel的设为高优先级*/
/* Enable error interrupts by default */
/*将IPU_INT_CTRL(5),IPU_INT_CTRL(6),IPU_INT_CTRL(9),IPU_INT_CTRL(10)这几个寄存器设置为默认值0xFFFFFFFF,这几个寄存器应该是错误中断使能的寄存器。*/
if (!bypass_reset)
/*同样是这个bypass_reset参数,暂时不知道它是多少。*/
register_ipu_device(ipu, id);
/*注册ipu_device设备,这个函数在ipu_device.c文件中*/
pm_runtime_enable(&pdev->dev);
/*使能设备的电源管理*/
return ret;
在看这个函数之前,先对ipu内部的channel有所了解,先来看看ipu内部的flow的定义:
对于每个flow,代表数据从摄像头采集到显示(或者保存在内存中)的过程中的一个分步骤,每个flow都对应一个或几个channel,而每个channel的数据流通过程中,是通过DMA传输的,所以每个channel里面又包含一个或几个dma_channel。在这不太好理解,看看手册中的介绍(captureflow):
其中上面和程序中所说的channel对应图中的Taskschain,对于这个channel的理解需要根据ipu内部架构图来理解,如下所示:
以上面CSI0-->SMFC-->MEM这个channel为例,它代表数据从CSI经过SMFC到达内存MEM中,因为数据是从CSI中获得的,所以physicalDMA channel中的videoinput为空,而数据从这个channel输出的话,就需要通过DMAchannel了,从图上可以看出来,physicalDMA channel中的videooutput可以为IDMAC_CH_0~ IDMAC_CH_3。
同样对于captureflow,还可以通过CSI0-->SMFC--->MEM和CSI0-->VDIC-->MEM这两种方式将摄像头采集到的数据存放到内存中。
再来看手册中的Processingflows的图:
以MEM-->IC-->MEM这个channel为例,根据IPU内部框架图可以看到,从内存中取出数据,经过IC处理以后再放入内存中,那么取出数据的时候,就使用到DMAchannel了,从这个图中可以看出来,使用的是IDMAC_CH12,再次放入内存中的时候就使用到IDMAC_CH20。
对于其他channel就暂时不一一分析了。再来看看程序中是怎样定义channel的。
内核中使用ipu_channel_t枚举来表示一个channel:
这个枚举就包含了所有的channel,如果对于上面讲解的过程理解的话,很容易根据channel的过程在这个枚举中找到对应的channel名字。同时可以看到在channel通过_MAKE_CHAN宏的构造过程中,每个channel里面都包含了输入输出dmachannel号。
分析完对channel的理解过程以后,再来看看具体的函数实现:
ipu_init_channel函数
/*这个函数有3个参数,第一个参数ipu代表正在使用的ipu。第二个参数是想要初始化的channel,它其实就是一个数字,可以理解为ID,第三个参数params是想要将这个channel初始化成什么样子,它里面包含channel的一些信息,会根据params参数来初始化这个channel。关于这个结构体的详细讲解可以看《ipu_channel_params_t结构体详解》*/
/*看注释,可以看出来,上面一个函数如果执行成功的话,它的返回值是1,所以在下面需要将这个ret清零。跟踪源码,确实是这样的。*/
/*在初始化每一个channel的时候都需要重新使能错误中断。*/
/*这个ipu_soc结构体里面的channel_init_mask项是uint32_t类型的,每个channel对应这个数字里面的某一位,如果这个channel进行过初始化操作的话,就将它的那一位置1.通过这个来判断其中的某一个channel是否进行过初始化。所以在这个初始化函数中肯定有将它置位的操作,我们搜索源码可以发现,在这个函数的后面确实有这样的操作。先在这粘贴一下:
ipu->channel_init_mask|= 1L << IPU_CHAN_ID(channel);
*/
ipu_conf = ipu_cm_read(ipu, IPU_CONF);
/*下面的switch判断语句就是根据channel的值来初始化不同的channel,所以这么多case就包括了所有的channel。*/
/*ipu->chan_is_interlaced是一个bool型的数组,根据params里面的csi_mem.interlaced参数,来设置ipu->chan_is_interlaced数组的某一项。怎么确定是数组的第几项?根据channel的值通过channel_2_dma函数为它在数组中挑选一个位置,然后将这个位置设置为true或false。*/
/*根据params->csi_mem.csi来确定是ipu->csi_channel数组的第几项,然后将channel赋给它。这个ipu->csi_channel数组包含两项,意思就是每个ipu有两个csi接口,每个接口选择哪个channel,都在这里进行保存。*/
/*params中csi_mem.mipi_en是个bool类型的值,如果它为1的话,表示使用的是mipi引脚。
下面来看看ipu_conf寄存器:
这里设置的是ipu_conf寄存器的28或者29位,看看这两位的介绍:
这两位的含义是选择CSI的数据来源,我们知道,CSI接口可以接并行接口或者MIPI接口,驱动会根据mipi_en的值来选择将这两位的数据来源设置为并口或者MIPI接口。如果是MIPI接口的话,需要通过_ipu_csi_set_mipi_di函数来设置MIPI相关的寄存器。
同时,不论是哪一种接口,都需要使用SMFC,就需要通过_ipu_smfc_init函数来初始化smfc的SMFC_MAP寄存器,在这个_ipu_smfc_init函数中根据不同的case选择不同的SMFC_MAP_CH;然后再调用_ipu_csi_set_mipi_di函数来初始化CSI_MIPI_DI寄存器。这个寄存器是mipidata identifier的意思。
*/
/*最后通过_ipu_csi_init函数初始化csi。*/
/*上面的channel流程就是下图这种模式:数据从CSI中通过SMFC直接到达IDMAC,然后通过IDMAC来进行数据的存放等操作。
*/
/*这个ipu->using_ic_dirct_ch是个ipu_channel_t类型的变量,usingic direct channel的意思,只能使用ICchannel而不能使用VDIchannel,所以是MEM_VDI_PRP_VF_MEM和MEM_VDI_MEM的话就会报错。最后再把这一位置位成CSI_PRP_ENC_MEM。*/
/*这个ipu_soc结构体中有一项ipu_channel_tcsi_channel[2];因为每个ipu有两个csi,所以这个数组里面保存的是每个csi选用的channel通道。在每一个需要用到csi接口的channel中都需要设置这一项,可以看到在这个switch中前几个用到csi的case,里面都设置了这一项。*/
/*同时在params这个参数中,它是ipu_channel_params_t类型的联合,其中包含的两个关于csi的结构体csi_mem和csi_prp_enc_mem,这两个结构体里面都含有一个uint32_tcsi,就是通过它来确定想要使用的是哪一个csi设备。 */
/*这个channel没有用到smfc,所以不用初始化它,其他跟上面那个channel一样。设置IPU_CONF寄存器中的28,29位。如果是MIPI接口的话,还需要通过_ipu_csi_set_mipi_di函数来初始化MIPI相关的寄存器。*/
/*设置ipu_conf中的IPU_CONF_IC_INPUT位和CSI_SEL位。这两位的含义如下:
在初始化这个channel的时候,我们应该首先想到这个channel使用到了IPU内部的哪些模块,在上面一个channel中,使用到了CSI和SMFC这两个模块,所以需要设置ipu_conf寄存器中的28,29数据来源位,选择数据是从并口来的还是从MIPI接口来的,然后分别初始化CSI和SMFC即可。
对于本次这个channel,CSI-->IC(prpenc)-->MEM,同样的思想,首先想想这个channel使用了哪几个模块,使用了CSI和IC模块,然后同样设置ipu_conf寄存器中的28,29数据来源位,然后就是设置30和31位了,30位设置IC的输入数据是从哪里来的,从IPU内部框架中可以看出来,输入IC的数据只能是VDI和CSI,所以这里设置30位为0。但是CSI的话还有两个呢,这就需要设置31位来确定是哪一个CSI设备了。
*/
/*将IPU_FS_PROC_FLOW1寄存器里面的FS_ENC_IN_VALID位清零。
这里设置的就是IPU_FS_PROC_FLOW1寄存器的30位,这一位的含义如上所示,它表示是否应该跳过内存中的buffer。这一位是根据RWS_EN来选择的。那么RWS_EN又是什么?在IC_CONF寄存器中找到了它,如下所示:
其中RWS是rawsensor的意思,一般sensor的输出数据可以为YUV,JPEG,RGB,RAW几种格式,其中这个RAW格式是未经处理、也未经压缩的格式,记录了数码相机传感器的原始信息,可以把RAW概念化为“原始图像编码数据”或更形象的称为“数字底片”。所以RWS_EN表示摄像头输出的数据是否是原始的RAW数据。
当RWS_EN为0时,数据直接从CSI输入到IC中,所以IPU_FS_PROC_FLOW1这个寄存器就需要设置跳过从内存中到达IC的数据,将IPU_FS_PROC_FLOW1寄存器的ENC_IN_VALID位清零。
*/
/*最后通过_ipu_csi_init函数初始化csi,同时通过_ipu_ic_init_prpenc函数预处理编码,这个函数在ipu_ic.c中。*/
/*至此,这个CSI_PRP_ENC_MEMchannel简单分析完毕,它的流程如下图所示:
*/
/*与上面那个CSI_PRP_ENC_MEMchannel一样,这个ipu->using_ic_dirct_ch只能置位成当前这个CSI_PRP_VF_MEMchannel。*/
/*与上一个函数不同的是,设置的IPU_FS_PROC_FLOW1寄存器的VF_IN_VALID位,原理是相同的。*/
/*除了最后一个函数,其他都与上一个case一样,最后一个函数_ipu_ic_init_prpvf同样在ipu_ic.c中。*/
/*这个CSI_PRP_VF_MEMchannel的流程如下所示:
*/
/*bad_pixfmt的意思可以在查看_ipu_ch_param_bad_alpha_pos函数的时候大致了解,在IPUv3IDMAC中对于32bpp的格式有个bug,所以对于32bpp的都返回true,对于其他返回false。*/
/*下面这个涉及到alpha通道的一些知识,
《RGBA》里面有简单介绍。*/
/*上面的代码,会根据ipu_channel_params_t里面的mem_prp_vf_mem这一项的graphics_combine_en来决定是否使能secondchannel,根据mem_prp_vf_mem这一项的alpha_chan_en来决定是否使能thirdchannel。如果使能的话,就会设置ipu_soc中的sec_chan_en[]和thrd_chan_en[]这两项,根据channelID号来置位。*/
/*其他函数大致都相似,就是细节不太相同,比如将IPU_FS_PROC_FLOW1寄存器的FS_VF_IN_VALID位设置为1;_ipu_ic_init_prpvf函数的第三个参数是false等等,_ipu_ic_init_prpvf函数的第三个参数是bool类型的变量,true代表IC的数据来源是CSI,false代表IC的数据来源不是CSI。*/
/*注意这个函数中设置了ipu_soc结构体中的sec_chan_en和thrd_chan_en,是两个bool型的数组,从名字上来看就是secondchannel enable和thirdchannelenable的意思,我认为MEM_PRP_VF_MEM这个channel是可以和其他某些channel一起启用的(在手册的CM模块对于IPUmainflows进行了详细的讲解,在后面具体分析)。根据params参数中mem_prp_vf_mem.graphics_combine_en置位的话,就会启用第二个channel,如果params参数中mem_prp_vf_mem.alpha_chan_en继续置位的话,就继续启用第三个channel。将启用了几个channel这些信息都保存在ipu_soc结构体中。*/
/*大部分设置都是相似的,在这个channel中使用到了VDIC,所以需要设置IPU_FS_PROC_FLOW1寄存器中有关VDIC的位:VDI_SRC_SEL,手册中的解释如下:
从程序中可以看到,只是将这些位清空了,所以默认的VDIC的数据来源是ARM平台。
同样,设置IC初始化为prpvf模式,然后通过_ipu_vdi_init初始化VDIC,_ipu_vdi_init在ipu_ic.c中定义。*/
/*这个模式的流程是下面这张图:
*/
/*这几个channel不再分析了。*/
/*设置ipu_soc中的using_ic_dirct_ch,增加ic_use_count和vdi_use_count的引用计数,最终也是调用这个_ipu_vdi_init函数。这个函数中增加了vdi_use_count的引用计数,上面几个与这个channel相似的channel都没有增加这个引用计数。这个channel只使用了VDIC。*/
/*上面几个channel程序比较简单,就暂时不分析。*/
/*这个channel与MEM_PRP_VF_MEMchannel类似,它也是根据params参数启用了sec_chan_en和thrd_chan_en。*/
/*
*/
/*后面几个channel是关于显示的channel,他们与前面几个case的步骤是相似的,暂时不分析,用到的时候再分析。*/
小总结:
可以看出来,在ipu_init_channel函数中,会根据对应的channel来决定使用哪个模块。
比如对于CSI_MEM0~CSI_MEM3,就会使用SMFC(_ipu_smfc_init函数),然后通过_ipu_csi_init函数来指定数据的目的地址。
对于CSI_PRP_ENC_MEMchannel,就会使用IC,使用的是IC的prpenc功能,就需要使用_ipu_ic_init_prpenc函数,同样需要使用通过_ipu_csi_init函数来指定数据的目的地址。
对于CSI_PRP_VF_MEMchannel,需要使用到IC,但是使用的是IC的prpvf功能,就需要使用_ipu_ic_init_prpvf函数。同样需要使用通过_ipu_csi_init函数来指定数据的目的地址。
上面几个函数都使用到了CSI设备,所以都使用了_ipu_csi_init函数来指定数据的目的地址。对于有些channel没有使用CSI设备,肯定就不用使用_ipu_csi_init这个函数了。
比如对于MEM_VDI_MEMchannel,它使用到VDI模块,只需要通过_ipu_vdi_init函数来设置即可。
比如MEM_ROT_VF_MEMchannel,它需要使用到IC的rotate和viewfinder功能,就需要通过_ipu_ic_init_rotate_vf函数来设置了。
ipu_init_channel_buffer函数
/*通过channel_2_dma函数将channel根据type提取出使用了哪个dmachannel。对于这个channel_2_dma函数,因为每个channel需要使用到dmachannel,在输入的时候可能使用,在输出的时候也可能使用,所以需要传入一个type类型来从channel中提取出所需要的dmachannel号。*/
/*对传入的stride参数进行判断。*/
/*如果是IC和IRTchannel的话,函数传入的width参数必须是8pixels的整数倍,否则就返回错误。*/
/*这个函数比较绕,首先来看函数传入的参数里面有三个dma_addr_t类型的变量phyaddr_0,phyaddr_1和phyaddr_2。这三个变量每一个都对应一个buffer的基地址。_ipu_is_trb_chan函数是判断dma_chan是否支持3buffers,如果支持的话,就肯定不会执行下面的dev_err语句;如果不支持3buffers的话,那传入的参数里面phyaddr_2就必须为0,否则就会执行dev_err语句(都不支持3buffers了,那么第三个buffer的地址肯定为0)。*/
/*这个判断语句是phyaddr_1存在的话就肯定不会执行dev_err语句,如果phyaddr_1和phyaddr_2都不存在的话,就会报错。*/
/*在_ipu_ch_param_init函数内部,首先根据传入的pixel_fmt,width, height, stride, u, v, 0, phyaddr_0, phyaddr_1,phyaddr_2的值来设置一个structipu_ch_param类型的params变量,然后将这个变量通过fill_cpmem函数写到通过ipu,dma_chan这两个参数确定的CPMEM中。如果phyaddr_2参数不为空的话,还需要通过fill_cpmem函数将params的值写入ipu和sub_ch确定的寄存器的基址中。这个_ipu_ch_param_init函数很重要。*/
/*设置alpha通道的一些信息,上面这些子函数都分析了,他们就是设置dma_chan对应的channel的某些位。*/
/*设置rot_mode参数在dma_chan中对应的某些位。*/
/*根据不同的channel设置burstlength的不同值。*/
/*设置interlaced参数。*/
/* 如果是 ic, irt或者 vdi_out channel的话,就需要调用_ipu_ic_idma_init函数来初始化ic和idma,这个函数在ipu_ic.c中定义。 */
/*如果是smfcchannel的话,就直接根据channel参数的值设置(burst_size)即可,_ipu_smfc_set_burst_size这个函数在ipu_capture.c中定义。*/
/*设置AXIprotocol id,这些AXIid是在ipu_probe函数中,从ipu_platform_type中获取到的。*/
/*设置idmac寄存器的一些参数。*/
/*上面这些步骤的意思是:如果(phyaddr_2&& ipu->devtype >=IPUv3EX)这个条件满足的话,就意味着这个channel支持triplebuffer MODE,所以先将寄存器中doublebuffer MODE的位清零,然后将triplebuffer MODE的位置位。至于是哪个寄存器。。。。通过IPU_CHA_DB_MODE_SEL这个宏可以看出来应该是CM寄存器。*/
/*设置idmac寄存器中关于triplebuffer的某些位*/
在这个函数中,有很多有特殊含义的行参,比如说stride,u_offset,v_offset等等,在这里分析的话,也不太懂是什么含义,不如在分析应用程序的时候,每个函数都有传入的实参值,那时候再具体分析。同时这个函数中用到了很多ipu_param_mem.h头文件中的函数,来填充CPMEM中的某一位或者某几位,下面就来分析ipu_param_mem.h这个头文件。
1.下面这两个结构体是本文件的核心结构体。
因为CPMEM是两个160位的word,所以每个word使用5个uint32_t类型来表示,同时有两个word。
2.这个宏暂时不分析,在后面用到的时候再分析。
#define ipu_ch_param_addr(ipu, ch) (((struct ipu_ch_param *)ipu->cpmem_base) + (ch))
3._param_word宏
这个宏有两个参数,第一个参数是一个起始地址值,它一般是一个指针,在宏中会进行强制类型转化;第二个参数是第几个word,看ipu_ch_param结构体,它的取值为0和1。
这个宏的意思是根据base这个起始地址值取到里面的第w个word的data数据段。
4.ipu_ch_param_set_field宏
#define ipu_ch_param_set_field(base, w, bit, size, v) { \
这个宏有5个参数,前两个参数与_param_word宏中的参数相同,它们也确实是给_param_word宏使用的。第三个参数bit表示某一个数据在通过_param_word宏所找到的data数据段中的准确起始bit位,因为data数据段是uint32_tdata[5]的,所以这个值的范围为0~160。size表示数据所占的位数。v表示传入的数据字面值。
这个宏的意思是首先通过(bit)/32来计算出这个数据的起始bit在data数组成员中的哪一个(因为data数组有5个成员)。然后off表示这个数据在data数组成员中的偏移值。然后通过_param_word(base,w)[i]来找到对应的data数组成员。同时这个宏中也给出了这个数据所占的位数:size,如果bit所在的data数组成员放不下这么多size数的话,就需要在data数组中的下一个数组成员中存储剩下的bit。
注意在数据的存储过程中涉及到大端小端的问题,对大端小端的解释:http://www.cnblogs.com/wuyuegb2312/archive/2013/06/08/3126510.html#《轻松记住大端小端的含义(附对大端和小端的解释)》
以下面这个函数中的各个数据为例来解释一下:
首先以ipu_ch_param_set_field( p, 1, 116, 3, red_width – 1)为例:
解释:
i= 116/32= 3;
off= 116%32 = 20;
_param_word(base,w)[i] |= (v) << off
==>p->word[w].data[i]|= (v) << off
==>p->word[1].data[3] |= (red_width – 1)<<20;
如下图所示:
然后继续往下执行:
同样,它最终就会在119的位置存储(green_width– 1),在122的位置存储(blue_width– 1),在125的位置存储(alpha_width– 1)。
它们详细表示如下:
同样对于
它们详细表示如下:
再分析一个ipu_ch_param_set_field函数中w=1, bit = 125, size = 13 的情况,对大端小端理解的更清楚。
i= 125/32 = 3;
off= 125%32 = 29;
_param_word(base,w)[i] |= (v) << off
==>p->word[w].data[i] |= (v) << off
==>p->word[1].data[3] |= (v)<<29;
if(((bit)+(size)-1)/32 > i) (125+12)/32= 4 > 3成立,所以会执行if下面的语句:
_param_word(base,w)[i + 1] |= (v) >> (off ? (32 - off) : 0);
==>_param_word(base,w)[4] |= (v) >> 3
==>p->word[1].data[4]|=(v) >> 3
重要的部分我用红色标出了,
从这里可以看出来,它的存储方式是将v的前10位存在了data[4]中,而v的后3位存在了data[3]中,从这里可以看出来,数据的存储方式是小端模式。
5.ipu_ch_param_set_field_io宏
这个宏根据base,w,bit的值计算出寄存器的位置,然后将v的值写进去。
但是这个ipu_ch_param_set_field_io宏与上面那个ipu_ch_param_set_field宏有什么不同呢?
往下搜索源码可以发现,虽然这两个宏的第一个参数都是base,但是他们两个不相同:
而这个ipu_ch_param_addr宏就是本文件开始的那个宏
#defineipu_ch_param_addr(ipu, ch) (((struct ipu_ch_param *)ipu->cpmem_base)+ (ch))
这个宏根据ipu和ch两个参数来找到对应寄存器的基址,具体来说就是根据ipu_soc结构体里面存放的cpmem_base寄存器的地址,ch是一般是uint32_t类型的dmachannel,通过这两个参数来找到对应寄存器的基地址。所以这个cpmem_base寄存器是设置dmachannel的关键寄存器。
对比上面两条语句,可以发现ipu_ch_param_set_field宏用于设置structipu_ch_param结构体参数params中某些位的值。而ipu_ch_param_set_field_io宏根据传入的ipu和ch参数来找到某个寄存器的基址,然后修改这个寄存器中的某些位。
以下的几个宏都与这两种情况类似。
6.ipu_ch_param_mod_field宏
这个函数首先为size大小设置掩码,比如size=7,这个mask就等于111111(二进制),然后通过temp&= ~(mask << off)将这几位都清零,最后再通过temp| (v) << off将v的值写到这几位中。修改某些位值的时候,一定要先清零了再写。
7.ipu_ch_param_mod_field_io宏
这个宏与上一个宏类似,修改寄存器中某些位的值。
8.ipu_ch_param_read_field宏
这个宏的意思是读取某些位的值,这个宏最后的结果是temp1的值。
9.ipu_ch_param_read_field_io宏
这个宏的意思是读取某个寄存器中某些位的值,最后的结果是temp1。
10.__ipu_ch_get_third_buf_cpmem_num函数
这个函数的大致意思是从函数传入的参数ch中获取到第三个buffer的起始地址。
11._ipu_ch_params_set_packing函数
这个函数在前面分析了,它主要目的是设置第一个参数p里面的red_width,red_offset,green_width,green_offset等信息。这个函数在_ipu_ch_param_init函数中调用,比如下面这样:
_ipu_ch_params_set_packing(¶ms,5, 0, 6, 5, 5, 11, 8, 16);
通过这样调用,就分别设置了params中的RGB的信息,从上面可以看出来是RGB565格式的。
_ipu_ch_params_set_packing(¶ms,4, 4, 4, 8, 4, 12, 4, 0);
这样调用设置的是RGBA4444的格式。
12._ipu_ch_param_dump函数
这个函数是输出ipu_ch_param中的一些信息,就不分析了。
13.fill_cpmem函数
这个函数首先通过ipu_ch_param_addr函数根据ipu和ch参数取得dmachannel的基址,然后将params参数里面两个word里面的data数据填充到获得的这个基址中。这个函数被_ipu_ch_param_init函数中调用。
14._ipu_ch_param_init函数
/*将params参数里面的word[0]里面的125位到138位设置为(width- 1) */
/*将params参数里面的word[0]里面的138位到150位设置为((height/ 2) - 1) */
/*将params参数里面的word[1]里面的102位到116位设置为((stride* 2) - 1) */
/*将params参数里面的word[0]里面的138位到150位设置为(height- 1) */
/*将params参数里面的word[1]里面的102位到116位设置为(stride- 1) */
如果channel是8/9/10的话,从注释上来看是vdoa+vdichannel,用的是bandmode,会将params那几位设置成height/2和stride*2。
/*将params参数里面的word[1]里面的0位到29位设置为(addr0>> 3),29位到58位设置成addr1>> 3。原因是EBA是8字节对齐的,后面需要分析这里。*/
/*根据函数传入的pixel_fmt参数的至来决定设置params里面的哪些位。从这个switch语句中可以看出来params参数里面word[0]里面从107位开始的几位决定bits/pixel,word[1]里面从85位开始的几位决定pixformat,word[1]里面从78位开始的几位决定burstsize。*/
/*如果uv_stride存在的话,就设置params参数里面的word[1]的128位到142位的值为(uv_stride- 1) */
/*上面这一段代码是设置u_offset和v_offset的值。他俩分别位于params->word[0]里面的46~68和68~90位。*/
/*辛辛苦苦设置好了params的值,肯定需要将它使用起来,就是通过这个函数来将params填充到根据ipu和ch参数找到的基址中。*/
/*通过__ipu_ch_get_third_buf_cpmem_num函数来找到第三个buffer的基址。*/
最后这些设置是在某些情况下,比如之前分析过,可能会将几个channel一起启用。这个函数被ipu_common.c中的ipu_init_channel_buffer函数所调用。
15._ipu_ch_param_set_burst_size函数
这个函数用来设置或修改burst_size的值。因为在_ipu_ch_param_init初始化函数中,已经根据pixel_fmt的值设置了burst_size的初始值,在这里可以继续通过ipu和ch的值来修改他们的burst_size。这个函数被ipu_common.c中的ipu_init_channel_buffer函数所调用。
16._ipu_ch_param_get_burst_size函数
通过ipu和ch参数找到对应的寄存器然后读取它的值。这个函数被ipu_common.c中的ipu_init_channel_buffer函数所调用。
17._ipu_ch_param_get_bpp函数
通过ipu和ch参数找到对应的寄存器然后读取它的值。
18._ipu_ch_param_set_buffer函数
首先根据ipu和ch来获取基址,然后根据传入的bufNum参数来决定修改基址中的哪些位。这个函数被ipu_common.c中的ipu_update_channel_buffer函数调用。根据手册上面可以看出来,关于buffer的设置是在word[1]中从0~28,59~57,而且根据后面的分析,对于双buffer模式,这个bufNum的取值是0或者1.
19._ipu_ch_param_set_rotation函数
设置rotation参数,这个函数首先根据rot的值通过bitrev8函数从byte_rev_table数组中取出对应的值,然后将那个值设置到word[0]的119~121位。这个函数被ipu_common.c中的ipu_init_channel_buffer函数调用。
20._ipu_ch_param_set_block_mode函数
设置block_mode参数,这个block_mode参数应该是位于word[0]里面的117~119位。这个函数被ipu_common.c中的ipu_init_channel_buffer函数调用。
21._ipu_ch_param_set_alpha_use_separate_channel函数
/*根据option这个bool类型的值来设置ch对应的基址中word[1]的89位。*/
如果有第三个buffer的话,就设置sub_ch对应的基址中word[1]的89位。这个函数被ipu_common.c中的ipu_init_channel_buffer函数调用。
22._ipu_ch_param_set_alpha_condition_read函数
这个函数的名字里面有read,但是里面调用的是ipu_ch_param_mod_field_io函数,他用来将ch对应的基址里面的word[1]的149位修改为1。如果有sub_ch的话,就同时修改sub_ch所对应的基址。这个函数被ipu_common.c中的ipu_init_channel_buffer函数调用。
23._ipu_ch_param_set_alpha_buffer_memory函数
/*根据ch的值设置alp_mem_idx的值。*/
这个函数首先根据ch的值来确定alp_mem_idx。然后调用ipu_ch_param_mod_field_io将ch对应基址里面的word[1]里面的90~93位修改成alp_mem_idx的值。这个函数被ipu_common.c中的ipu_init_channel_buffer函数调用。
24._ipu_ch_param_set_interlaced_scan函数
首先通过ipu_ch_param_set_field_io函数将ch对应基址里面word[0]的113位设为1。如果存在sub_ch的话,将sub_ch对应的基址里面word[0]的113位设为1。之后通过ipu_ch_param_read_field_io函数读取ch对应基址里面word[1]的102~116位的值来求出stride的值。之后通过ipu_ch_param_mod_field_io函数将ch对应基址里面的word[1]的58~78位设置为(stride/ 8),将ch对应基址里面的word[1]的102~116位设置为(stride-1),如果存在sub_ch的话需要做同样的操作。这个函数被ipu_common.c中的ipu_init_channel_buffer函数调用。
25._ipu_ch_param_set_axi_id函数
根据传入的id参数来设置ch对应的基址里面word[1]的93~94位,这一位是关于axi_id的。
26._ipu_ch_param_get_axi_id函数
这个函数就是读取ch对应的基址里面word[1]的93~94位。这个函数被ipu_common.c中的ipu_ch_param_get_axi_id函数调用。
27.__ipu_ch_offset_calc函数
这个函数一共有11个参数,它的最终目的是根据前9个参数设置最后2个参数的值。这个函数被本文件中_ipu_ch_offset_update函数和ipu_common.c文件中ipu_get_channel_offset函数调用。
这个函数就是根据数据格式来算出u_offset和v_offset的值,以后根据各种格式来仔细算算。
28._ipu_ch_offset_update函数
从这个函数的名字可以看出来,ipuchannel offset update:ipuchannel偏移更新,这个函数首先通过上一个__ipu_ch_offset_calc函数来计算出&u_offset和&v_offset,然后根据传入的ipu和ch参数来读取ch对应基址word[0]的46~68位中的老&u_offset偏移值和ch对应基址word[0]的68~90位中的老&v_offset偏移值,然后修改它们。重点还是u_offset和v_offset这两个值。
29._ipu_ch_params_set_alpha_width函数
这个函数同样就是设置ch对应基址里面word[1]的125~128位,将它设置为(alpha_width– 1),这一位应该就是关于alpha_width的。
30._ipu_ch_param_set_bandmode函数
这个函数同样就是设置ch对应基址里面word[0]的114~117位,将它设置为(band_height– 1),这一位应该就是关于band_height的。
31._ipu_ch_param_bad_alpha_pos函数
这个注释里面写的很清楚了,当IPUv3IDMAC 在从一个alpha组件位于最重要8位的图像位面读取32bpp像素的时候有一个bug,所以当pixel_fmt为32位的时候,就返回true,否则返回false。
ipu_request_irq函数
这个函数被很多函数所调用,以ipu_device.c中do_task函数为例,这个do_task函数首先通过:
irq= get_irq(t);来获取到中断号,然后通过:
ret= ipu_request_irq(ipu, irq, task_irq_handler, 0, NULL, t);来将task_irq_handler这个中断服务程序(InterruptService Routines,ISR)与irq中断号绑定。那么再看查看这个ipu_request_irq中都做了哪些工作:
在structipu_soc中,有一项structipu_irq_node irq_list[IPU_IRQ_COUNT];
每一个中断号都在这个数组中对应一项,如果这个中断号没有被申请的话,这一项就会为空。通过这个ipu_request_irq函数来申请的时候,就会将数组中这一项根据函数传入的参数都填充进去。
/*ipu->channel_enable_mask中每一位对应一个channel是否使能了。首先检查要使能的这个channel是否已经使能了,如果已经使能了的话就会报错。*/
/*通过这两步将channel转化成dma_ch,在之前分析过这个函数,会根据type类型从channel中选取出输入或者输出的dmachannel. */
/*根据ipu参数里面的引用计数来决定将ipu_conf寄存器中的对应位置位。最终将这些值都写到这个ipu_conf寄存器中。*/
/*通过这几个函数来将in_dma和out_dma的值写到ipu里面的idmac寄存器中。*/
/*ipu_soc结构体中sec_chan_en是一个bool类型的数组,它会根据channel通过IPU_CHAN_ID转化成的数字来从这个数组中找到对应的一项,如果使能了secondchannel的话,就是true,否则就是false。同时channel的需要是PP,PRP_VF,VDI_PRP_VF的情况,如果条件都成立的话,同样会设置idmac寄存器里面的某些位。*/
/*这一段代码判断的是thirdchannel是否使能同时此时对应的channel是PP和PRP_VF。如果是使能了thirdchannel的话,肯定已经使能了secondchannel,所以需要同时在idmac中设置thrd_dma和sec_dma的值。*/
/*这一段代码判断的是thirdchannel是否使能同时此时对应的channel是BG和FG。如果是使能了thirdchannel的话,肯定已经使能了secondchannel,所以需要同时在idmac中设置thrd_dma和sec_dma的值。*/
/*这几个通道是关于输出显示的通道,个人理解是displaycontroll sync, background sync和foregroundsyncchannel,这时肯定不能仅仅设置idmac寄存器,同时需要调用_ipu_dp_dc_enable函数来使能显示设备,这个函数在ipu_disp.c中定义。*/
/*如果是in_dma和out_dma使用到了ic,irt,vdi_out的话,都需要通过_ipu_ic_enable_task函数来使能ic,这个函数在ipu_ic.c中定义。*/
ipu->channel_enable_mask |= 1L << IPU_CHAN_ID(channel);
/*这个channel_enable_mask是一个uint32_t类型的掩码,如果使能了哪个channel的话,就将这个channel对应的位置1。同时也会在这个函数开始的地方通过它来判断一个channel是否已经使能过了。*/
总结一下,这个函数都做了哪些事情:
(1)既然是使能channel函数,设置的首要寄存器就是ipu_conf,会根据ipu_soc结构体里面的各个子模块的引用计数来将ipu_conf中对应的位使能。
(2)每一个channel都会使用到一个或者多个dmachannel,那么同样的,需要将这几个dmachannel所对应的寄存器的位使能,对应的寄存器是IPUx_IDMAC_CH_EN_1或者IPUx_IDMAC_CH_EN_2.
至此,ipu_common.c文件中的重要函数都分析完毕。
/*ipu->channel_enable_mask中每一位对应一个channel是否使能了。首先检查要使能的这个channel是否已经使能了,如果已经使能了的话就会报错。*/
/*通过这两步将channel转化成dma_ch,在之前分析过这个函数,会根据type类型从channel中选取出输入或者输出的dmachannel. */
/*根据ipu参数里面的引用计数来决定将ipu_conf寄存器中的对应位置位。最终将这些值都写到这个ipu_conf寄存器中。*/
/*通过这几个函数来将in_dma和out_dma的值写到ipu里面的idmac寄存器中。*/
/*ipu_soc结构体中sec_chan_en是一个bool类型的数组,它会根据channel通过IPU_CHAN_ID转化成的数字来从这个数组中找到对应的一项,如果使能了secondchannel的话,就是true,否则就是false。同时channel的需要是PP,PRP_VF,VDI_PRP_VF的情况,如果条件都成立的话,同样会设置idmac寄存器里面的某些位。*/
/*这一段代码判断的是thirdchannel是否使能同时此时对应的channel是PP和PRP_VF。如果是使能了thirdchannel的话,肯定已经使能了secondchannel,所以需要同时在idmac中设置thrd_dma和sec_dma的值。*/
/*这一段代码判断的是thirdchannel是否使能同时此时对应的channel是BG和FG。如果是使能了thirdchannel的话,肯定已经使能了secondchannel,所以需要同时在idmac中设置thrd_dma和sec_dma的值。*/
/*这几个通道是关于输出显示的通道,个人理解是displaycontroll sync, background sync和foregroundsyncchannel,这时肯定不能仅仅设置idmac寄存器,同时需要调用_ipu_dp_dc_enable函数来使能显示设备,这个函数在ipu_disp.c中定义。*/
/*如果是in_dma和out_dma使用到了ic,irt,vdi_out的话,都需要通过_ipu_ic_enable_task函数来使能ic,这个函数在ipu_ic.c中定义。*/
ipu->channel_enable_mask |= 1L << IPU_CHAN_ID(channel);
/*这个channel_enable_mask是一个uint32_t类型的掩码,如果使能了哪个channel的话,就将这个channel对应的位置1。同时也会在这个函数开始的地方通过它来判断一个channel是否已经使能过了。*/
总结一下,这个函数都做了哪些事情:
(1)既然是使能channel函数,设置的首要寄存器就是ipu_conf,会根据ipu_soc结构体里面的各个子模块的引用计数来将ipu_conf中对应的位使能。
(2)每一个channel都会使用到一个或者多个dmachannel,那么同样的,需要将这几个dmachannel所对应的寄存器的位使能,对应的寄存器是IPUx_IDMAC_CH_EN_1或者IPUx_IDMAC_CH_EN_2.
至此,ipu_common.c文件中的重要函数都分析完毕。
在ipu_common.c文件中,会调用到这个文件很多底层的函数,来设置视频捕获设备中底层的一些操作寄存器。这个文件就直接从头至尾开始分析:
1._ipu_csi_mclk_set函数
这个函数是设置csi设备的mclk时钟参数,首先根据想要设置的pixel_clk时钟参数计算出来需要设置的div_ratio值,然后通过ipu_csi_read函数读出原来的CSI_SENS_CONF寄存器的值,然后将div_ratio设置进去,再通过ipu_csi_write将值重新写到CSI_SENS_CONF寄存器中。
2.ipu_csi_init_interface函数,这个函数被mxc_v4l2_capture.c的mxc_v4l_open函数和mxc_v4l2_s_param函数调用。
/*首先根据传进来的不同pixel_fmt参数的值,设置cfg_param.data_fmt的值。*/
/*设置CSI_SENS_CONF寄存器的值。*/
/*根据传进来的width和height参数,设置CSI_SENS_FRM_SIZE寄存器的值。*/
/*根据cfg_param.clk_mode参数的不同值来设置CSI_CCIR_CODE_1,CSI_CCIR_CODE_2和CSI_CCIR_CODE_3寄存器的值,同时在IPU_CSI_CLK_MODE_CCIR656_INTERLACED的情况下也会参考width和height的值进行设置。其中会调用到_ipu_csi_ccir_err_detection_enable函数,这个函数在后面分析。在下面13中分析。*/
3.ipu_csi_get_sensor_protocol函数,这个函数会在ipu_fg_overlay_sdc.c文件中的csi_enc_setup函数中调用:
这个函数的大致意思就是通过指定的ipu的某一个csi,返回这个csi设备的CSI_SENS_CONF寄存器里面已经配置好的值。
4.ipu_csi_enable_mclk函数,在mxc_v4l2_capture.c文件中调用了ipu_csi_enable_mclk_if函数,这个函数其实就是ipu_csi_enable_mclk函数。
这个函数根据传入的flag参数来决定使能还是关闭时钟,如果flag为true的话,就调用clk_enable函数使能时钟,如果flag参数为false的话,就调用clk_disable函数来关闭时钟。
5.ipu_csi_get_window_size函数,这个函数在ipu_prp_vf_sdc_bg.c,ipu_prp_vf_sdc.c和ipu_prp_enc.c中都有调用。
这个函数就是从指定ipu的某个csi设备中,读取CSI_ACT_FRM_SIZE寄存器的值保存在*width和*height中返回。
6.ipu_csi_set_window_size函数,这个函数在mxc_v4l2_capture.c中的mxc_v4l2_s_param,mxc_v4l_open,init_camera_struct,和mxc_v4l_do_ioctl的VIDIOC_S_CROP函数中都有调用。
这个函数就是将传进来的width和height参数设置到CSI_ACT_FRM_SIZE寄存器中。
7.ipu_csi_set_window_pos函数,这个函数和上一个ipu_csi_set_window_size函数是一起使用的。想要确定一个窗口的大小,只需要知道它的左上角坐标和窗口的长度/宽度就可以设置了。
将传进来的left和top参数对应设置到CSI_OUT_FRM_CTRL寄存器中即可。
8._ipu_csi_horizontal_downsize_enable函数,这个函数暂时没人调用。。。
函数的大致意思是设置csi设备允许水平方向上的小型化(缩放),这个概念不好理解,但是底层的操作很简单,就是将CSI_OUT_FRM_CTRL寄存器的CSI_HORI_DOWNSIZE_EN位置位即可。
9._ipu_csi_horizontal_downsize_disable函数,是上一个函数的反函数。
同样的,想要关闭这个功能,只需要将SI_OUT_FRM_CTRL寄存器的CSI_HORI_DOWNSIZE_EN位清零即可。
10._ipu_csi_vertical_downsize_enable函数
这个函数就是允许csi设备垂直方向上的小型化(缩放),同样,只需要将CSI_OUT_FRM_CTRL寄存器的CSI_VERT_DOWNSIZE_EN位置位即可。
11._ipu_csi_vertical_downsize_disable函数
这个函数是第10个函数的反函数,想要关闭垂直方向上的小型化这个功能,只需要将CSI_OUT_FRM_CTRL寄存器的CSI_VERT_DOWNSIZE_EN位清零即可。
12._ipu_csi_set_test_generator函数
这个函数根据active参数的值决定是否启动这个测试发生器的功能。如果active为false的话,就将CSI_TST_CTRL寄存器中的CSI_TEST_GEN_MODE_EN位清零,如果active为true的话,就根据传入的pix_clk参数设置时钟,然后将传入的R,G,B色彩写到CSI_TST_CTRL寄存器中。
这个函数不知道具体想要实现什么功能,也没有其他的函数调用它,所以先分析这些。
13._ipu_csi_ccir_err_detection_enable函数
这个函数在ipu_csi_init_interface函数中使用了它,我们在这分析:
它只是将CSI_CCIR_CODE_1寄存器的CSI_CCIR_ERR_DET_EN位置位了。启用CCIR交错模式的错误校验和修正。
14._ipu_csi_ccir_err_detection_disable函数
这个函数是_ipu_csi_ccir_err_detection_enable函数的反函数。
15._ipu_csi_set_mipi_di函数,这个函数在ipu_common.c中的ipu_init_channel函数里调用.
它就是根据这个传入的num值来修改CSI_MIPI_DI寄存器中的值,我在用户手册中搜索到的寄存器的名字叫做CSI1Data Identifier Register (IPUx_CSI1_DI),它应该就是为了区分数据来源是哪个流。
16._ipu_csi_set_skip_isp函数
它主要是用来设置CSI_SKIP寄存器的,根据传入的skip和max_ratio两个值来修改。查看用户数据手册可以发现,这个寄存器可以设置的地方有3个,这个函数可以修改skip和max_ratio,下面那个函数可以修改另外一个smfc。
17._ipu_csi_set_skip_smfc函数
这个函数与上面那个类似,只是需要通过3个参数来确定修改CSI_SKIP中的哪些位。以后再具体查看这些位的含义。
18._ipu_smfc_init函数,它在ipu_common.c文件中的ipu_init_channel函数中调用了。
看函数的注释,MapCSI frames to IDMACchannels.这个SMFC位于csi设备与IDMAC之间的位置,意思应该就是映射csi的frame到IDMAC中。操作很简单,根据channel参数的值来设置SMFC_MAP寄存器中的值。
19._ipu_smfc_set_wmc函数
根据channel的值来设置SMFC_WMC寄存器中的不同位。函数意思不懂。
20._ipu_smfc_set_burst_size函数,在ipu_common.c中的ipu_init_channel_buffer中调用了。
函数作用是设置IDMACchannel的burstsize(脉冲串?),根据传入的bs参数设置SMFC_BS寄存器即可。
21._ipu_csi_init函数,它在ipu_common.c中的ipu_init_channel函数中调用了。
这个函数初始化csi设备,它根据传入的channel的不同,设置不同的csi数据目的地,然后将这个目的地设置到CSI_SENS_CONF寄存器中。感觉这个channel在ipu内部的意思就是ipu内数据流通的过程,每个channle里面都含有目的地等信息。
22._ipu_csi_wait4eof函数,在ipu_common.c中的ipu_disable_csi函数中调用了。
它的中断处理函数如下:
这个函数中,首先根据channel的不同值设置不同的irq触发方式,然后初始化完成量,然后调用ipu_request_irq函数申请中断,这个函数在ipu_common.c中定义。如果发生中断后,就会调用到这个中断处理函数,在中断处理函数中通过complete()函数来唤醒阻塞在ipu->csi_comp上的首个线程。wait_for_completion_timeout函数表示,如果超过一定时间没有发生这个中断,就直接结束等待。
关于这个完成量的解释和操作函数,可以看《Linux的completion机制.pdf》和《线程的约会completion.pdf》两个文件。
至此,这个文件简单分析完毕。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。