Linux3.X下的Samsung MFC(Multi Format Codec) Firmware

作者:咕唧咕唧liukun321

來自:http://blog.csdn.net/liukun321


三星系列處理器,從S3c6410開始SOC上就集成了一個MFC(Multi Format Codec)多格式媒體編解碼器硬件模塊。並且三星的linux BSP開源了對它的驅動。看過MFC驅動的朋友,肯定發現了,在驅動初始化過程中,內核需要加載一個Samsung MFC 固件。沒有它MFC是用不了的。下面我們就看看,這個固件是怎麼和內核聯繫起來的。從哪能獲得固件。

先看MFC固件的加載,這個過程如下,以exynos4412 Linux 3.5 的BSP爲例,在linux-3.5\drivers\media\video\s5p-mfc\s5p_mfc.c:


static int s5p_mfc_open(struct file *file)
{
         structs5p_mfc_dev *dev = video_drvdata(file);
         structs5p_mfc_ctx *ctx = NULL;
         structvb2_queue *q;
         unsignedlong flags;
         intret = 0;
         exynos_cpufreq_lock_freq(1,MAX_CPU_FREQ);
#ifdef CONFIG_BUSFREQ_OPP
         dev_lock(dev->bus_dev,dev->device, BUSFREQ_400MHZ);
#endif
 
         mfc_debug_enter();
         dev->num_inst++;   /* It is guarded by mfc_mutex in vfd */
         /*Allocate memory for context */
         ctx= kzalloc(sizeof *ctx, GFP_KERNEL);
         if(!ctx) {
                   mfc_err("Notenough memory\n");
                   ret= -ENOMEM;
                   gotoerr_alloc;
         }
         v4l2_fh_init(&ctx->fh,video_devdata(file));
         file->private_data= &ctx->fh;
         v4l2_fh_add(&ctx->fh);
         ctx->dev= dev;
         INIT_LIST_HEAD(&ctx->src_queue);
         INIT_LIST_HEAD(&ctx->dst_queue);
         ctx->src_queue_cnt= 0;
         ctx->dst_queue_cnt= 0;
         /*Get context number */
         ctx->num= 0;
         while(dev->ctx[ctx->num]) {
                   ctx->num++;
                   if(ctx->num >= MFC_NUM_CONTEXTS) {
                            mfc_err("Toomany open contexts\n");
                            ret= -EBUSY;
                            gotoerr_no_ctx;
                   }
         }
         /*Mark context as idle */
         spin_lock_irqsave(&dev->condlock,flags);
         clear_bit(ctx->num,&dev->ctx_work_bits);
         spin_unlock_irqrestore(&dev->condlock,flags);
         dev->ctx[ctx->num]= ctx;
         if(s5p_mfc_get_node_type(file) == MFCNODE_DECODER) {
                   ctx->type= MFCINST_DECODER;
                   ctx->c_ops= get_dec_codec_ops();
                   /*Setup ctrl handler */
                   ret= s5p_mfc_dec_ctrls_setup(ctx);
                   if(ret) {
                            mfc_err("Failedto setup mfc controls\n");
                            gotoerr_ctrls_setup;
                   }
         }else if (s5p_mfc_get_node_type(file) == MFCNODE_ENCODER) {
                   ctx->type= MFCINST_ENCODER;
                   ctx->c_ops= get_enc_codec_ops();
                   /*only for encoder */
                   INIT_LIST_HEAD(&ctx->ref_queue);
                   ctx->ref_queue_cnt= 0;
                   /*Setup ctrl handler */
                   ret= s5p_mfc_enc_ctrls_setup(ctx);
                   if(ret) {
                            mfc_err("Failedto setup mfc controls\n");
                            gotoerr_ctrls_setup;
                   }
         }else {
                   ret= -ENOENT;
                   gotoerr_bad_node;
         }
         ctx->fh.ctrl_handler= &ctx->ctrl_handler;
         ctx->inst_no= -1;
         /*Load firmware if this is the first instance */
         if(dev->num_inst == 1) {
                   dev->watchdog_timer.expires= jiffies +
                                               msecs_to_jiffies(MFC_WATCHDOG_INTERVAL);
                   add_timer(&dev->watchdog_timer);
                   ret= s5p_mfc_power_on();
                   if(ret < 0) {
                            mfc_err("poweron failed\n");
                            gotoerr_pwr_enable;
                   }
                   s5p_mfc_clock_on();
                   ret = s5p_mfc_alloc_and_load_firmware(dev);
                   if(ret)
                            gotoerr_alloc_fw;
                   /*Init the FW */
                   ret= s5p_mfc_init_hw(dev);
                   if(ret)
                            gotoerr_init_hw;
                   s5p_mfc_clock_off();
         }
         /*Init videobuf2 queue for CAPTURE */
         q= &ctx->vq_dst;
         q->type= V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
         q->drv_priv= &ctx->fh;
         if(s5p_mfc_get_node_type(file) == MFCNODE_DECODER) {
                   q->io_modes= VB2_MMAP | VB2_DMABUF;
                   q->ops= get_dec_queue_ops();
         }else if (s5p_mfc_get_node_type(file) == MFCNODE_ENCODER) {
                   q->io_modes= VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
                   q->ops= get_enc_queue_ops();
         }else {
                   ret= -ENOENT;
                   gotoerr_queue_init;
         }
         q->mem_ops= (struct vb2_mem_ops *)&vb2_dma_contig_memops;
         ret= vb2_queue_init(q);
         if(ret) {
                   mfc_err("Failedto initialize videobuf2 queue(capture)\n");
                   gotoerr_queue_init;
         }
         /*Init videobuf2 queue for OUTPUT */
         q= &ctx->vq_src;
         q->type= V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
         q->io_modes= VB2_MMAP;
         q->drv_priv= &ctx->fh;
         if(s5p_mfc_get_node_type(file) == MFCNODE_DECODER) {
                   q->io_modes= VB2_MMAP | VB2_DMABUF;
                   q->ops= get_dec_queue_ops();
         }else if (s5p_mfc_get_node_type(file) == MFCNODE_ENCODER) {
                   q->io_modes= VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
                   q->ops= get_enc_queue_ops();
         }else {
                   ret= -ENOENT;
                   gotoerr_queue_init;
         }
         q->mem_ops= (struct vb2_mem_ops *)&vb2_dma_contig_memops;
         ret= vb2_queue_init(q);
         if(ret) {
                   mfc_err("Failedto initialize videobuf2 queue(output)\n");
                   gotoerr_queue_init;
         }
         init_waitqueue_head(&ctx->queue);
         mfc_debug_leave();
         returnret;
         /*Deinit when failure occured */
err_queue_init:
err_init_hw:
         s5p_mfc_release_firmware(dev);
err_alloc_fw:
         dev->ctx[ctx->num]= NULL;
         del_timer_sync(&dev->watchdog_timer);
         s5p_mfc_clock_off();
err_pwr_enable:
         if(dev->num_inst == 1) {
                   if(s5p_mfc_power_off() < 0)
                            mfc_err("poweroff failed\n");
                   s5p_mfc_release_firmware(dev);
         }
err_ctrls_setup:
         s5p_mfc_dec_ctrls_delete(ctx);
err_bad_node:
err_no_ctx:
         v4l2_fh_del(&ctx->fh);
         v4l2_fh_exit(&ctx->fh);
         kfree(ctx);
err_alloc:
         dev->num_inst--;
         mfc_debug_leave();
         returnret;
}

展開第83行函數ret = s5p_mfc_alloc_and_load_firmware(dev);


int s5p_mfc_alloc_and_load_firmware(struct s5p_mfc_dev *dev)
{
	struct firmware *fw_blob;
	size_t bank2_base_phys;
	void *b_base;
	int err;

	/* Firmare has to be present as a separate file or compiled
	 * into kernel. */
	mfc_debug_enter();
	err = request_firmware((const struct firmware **)&fw_blob,
				     "s5p-mfc.fw", dev->v4l2_dev.dev);
	if (err != 0) {
		mfc_err("Firmware is not present in the /lib/firmware directory nor compiled in kernel\n");
		return -EINVAL;
	}
	dev->fw_size = ALIGN(fw_blob->size, FIRMWARE_ALIGN);
	if (s5p_mfc_bitproc_buf) {
		mfc_err("Attempting to allocate firmware when it seems that it is already loaded\n");
		release_firmware(fw_blob);
		return -ENOMEM;
	}
	s5p_mfc_bitproc_buf = vb2_dma_contig_memops.alloc(
		dev->alloc_ctx[MFC_BANK1_ALLOC_CTX], dev->fw_size);
	if (IS_ERR(s5p_mfc_bitproc_buf)) {
		s5p_mfc_bitproc_buf = NULL;
		mfc_err("Allocating bitprocessor buffer failed\n");
		release_firmware(fw_blob);
		return -ENOMEM;
	}


只看關鍵部分,分析一下request_firmware((const struct firmware**)&fw_blob, "s5p-mfc.fw", dev->v4l2_dev.dev); 從函數名看,不難聯想其功能:加載固件。 其實就是把固件中的數據搬到fw_blob 的一個成員指向的內存中,具體怎麼搬,看後面分析。 說到這裏,我們有必要再看一下struct firmware 這個結構的定義

struct firmware {

         size_t size;

         const u8*data;

         struct page**pages;

};

該說說固件加載的過程了,request_firmware執行過程中,會爲實例化的struct firmware結構分配內存,並且函數將在 /sys/class/firmware 下創建一個以設備名爲目錄名的新目錄,其中包含3 個屬性:

loading :這個屬性應當被加載固件的用戶空間進程設置爲 1。當加載完畢, 它將被設爲 0。被設爲 -1 時,將中止固件加載。
data :一個用來接收固件數據的二進制屬性。在設置 loading 1, 用戶空間進程將固件寫入這個屬性。
device :一個鏈接到 /sys/devices 下相關入口項的符號鏈接。

一旦創建了 sysfs 入口項, 內核將爲設備產生一個熱插拔事件,並傳遞包括變量FIRMWARE的環境變量給處理熱插拔的用戶空間程序。FIRMWARE被設置爲提供給 request_firmware 的固件文件名。

用戶空間程序定位固件文件, 並將其拷貝到內核提供的二進制屬性;若無法定位文件, 用戶空間程序設置 loading 屬性爲 -1。

 

若固件請求在 10 秒內沒有被服務, 內核就放棄並返回一個失敗狀態給驅動。超時週期可通過 sysfs 屬性 /sys/class/firmware/timeout 屬性改變。

 

說了這麼多,其實還落了個重點。其實在執行request_firmware時還有這樣一個過程:判斷固件是否被編譯進內核,如果在內核編譯過程就加入了固件,上面說的那個過程就白扯了---用不到。看下面代碼:

fw_priv =_request_firmware_prepare(firmware_p, name, device, true,false);

if (IS_ERR_OR_NULL(fw_priv))

         returnPTR_RET(fw_priv);

 

這是request_firmware函數開頭的一小段代碼,_request_firmware_prepare 函數執行過程,判斷固件若被編譯入內核,則從內核全局的固件地址(指針)處往目標地址進行拷貝,也就是拷貝到structfirmware data成員指向的內存處(最終完成立了固件的加載),然後_request_firmware_prepare返回空指針,進而request_firmware函數也返回了。此過程代碼實現如下:

static struct firmware_priv *
_request_firmware_prepare(const struct firmware **firmware_p, const char *name,
			  struct device *device, bool uevent, bool nowait)
{
	struct firmware *firmware;
	struct firmware_priv *fw_priv;

	if (!firmware_p)
		return ERR_PTR(-EINVAL);

	*firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL);
	if (!firmware) {
		dev_err(device, "%s: kmalloc(struct firmware) failed\n",
			__func__);
		return ERR_PTR(-ENOMEM);
	}

	if (fw_get_builtin_firmware(firmware, name)) {
		dev_dbg(device, "firmware: using built-in firmware %s\n", name);
		return NULL;
	}
。。。。。
}

上面扯了一堆固件加載過程的東西,其實不止是MFC,幾乎所有的固件加載都是遵循上面的過程。

再來點實用的東西。

我們該從哪裏獲得固件呢?

1、找三星技術支持。

2、找第三方開發板售後。

3、從網上Down

前兩條就當是廢話,三星不會搭理我們這些小用戶。開發板售後?他們只會說:自己找一下,光盤裏呢!光盤裏是有,可我想要高版本的哪去搞?

這個也不難,google一下流行的發行版的linux內核分支,固件嘛好在是應用於全平臺的。立馬就有了。

下面附上我拿到的固件:

Samsung MFC 固件下載

共包含以下文件:
 * MFCv5 firmware (s5p-mfc.fw)
 * MFCv6 firmware, version 1 (s5p-mfc-v6.fw)
 * MFCv6 firmware, version 2 (s5p-mfc-v6-v2.fw)
 * MFCv7 firmware (s5p-mfc-v7.fw)
 * MFCv8 firmware (s5p-mfc-v8.fw)

在我的內核包裏,用的是s5p-mfc.fw,也就是MFCv5。

另外還找到了一些關於MFC固件版本與處理器對應關係的信息:

s5p-mfc-v7.fw: Used in exynos 5420

s5p-mfc-v8.fw: Used in exynos 5800

 

本文的內容就這麼多了,下篇文章希望能夠接上篇,分析Live555服務器流程。Live555用的時間短,文字碼出來,總感覺質量不行。好事多磨吧。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章