linux : firmware 簡介

我以為device要可以運作, 只要在linux安裝相關的driver即可
最近看到原來device除了driver之外, 還是需要firmware這東西才可以運作...
稍微做個筆記....

Firmware

基本上device要可以正常運作是需要driver和firmware.
driver可以看成是系統跟device溝通的橋樑. firmware則是執行在device上面的程式.
Firmware除了可以預先寫死在device上外,另一方式就是driver初始化device的時候. 從系統讀取firmware, 在寫入到device.

Firmware讀取流程

linux放置firmware的地方為/lib/firmware. 讀取firmware的時候就是透過此.
(1) 流程參考[2]

  1. Driver跟kernel要求 "ar9170.fw"
  2. kernel傳遞event給udev來要求firmware
  3. udev執行對應的script,把firmware傳送至kernel所創建的一個特殊檔案
  4. kernel從此特殊檔案讀取firmware, 再把資料傳送給driver
  5. driver再把資料傳給device

(2) 流程參考[3][5]
 1), kernel(driver):
    - calls request_firmware(&fw_entry, $FIRMWARE, device)
    - kernel searchs the fimware image with name $FIRMWARE directly
    in the below search path of root filesystem:
        User customized search path by module parameter 'path'[1]
        "/lib/firmware/updates/" UTS_RELEASE,
        "/lib/firmware/updates",
        "/lib/firmware/" UTS_RELEASE,
        "/lib/firmware"
    - If found, goto 7), else goto 2)

    [1], the 'path' is a string parameter which length should be less
    than 256, user should pass 'firmware_class.path=$CUSTOMIZED_PATH'
    if firmware_class is built in kernel(the general situation)

 2), userspace:
    - /sys/class/firmware/xxx/{loading,data} appear.
    - hotplug gets called with a firmware identifier in $FIRMWARE
      and the usual hotplug environment.
        - hotplug: echo 1 > /sys/class/firmware/xxx/loading

 3), kernel: Discard any previous partial load.

 4), userspace:
        - hotplug: cat appropriate_firmware_image > \
                    /sys/class/firmware/xxx/data

 5), kernel: grows a buffer in PAGE_SIZE increments to hold the image as it
     comes in.

 6), userspace:
        - hotplug: echo 0 > /sys/class/firmware/xxx/loading

 7), kernel: request_firmware() returns and the driver has the firmware
     image in fw_entry->{data,size}. If something went wrong
     request_firmware() returns non-zero and fw_entry is set to
     NULL.

 8), kernel(driver): Driver code calls release_firmware(fw_entry) releasing
         the firmware image and any related resource.

Trace driver Code

這邊trace位於drivers/net/ethernet/brocade/bna底下的兩個檔案bfa_ioc.c和bnad_ethtool.c.
bnad_ethtool.c這邊可以看到driver透過request_firmware函數去跟kernel要求firmware.
在寫入至device之後就透過release_firmware把firmware給釋放掉了.
而bfa_nw_flash_update_part和bfa_flash_write_send則是driver如何把firmware寫入至device的流程.

static int
bnad_flash_device(struct net_device *netdev, struct ethtool_flash *eflash)
{
    struct bnad *bnad = netdev_priv(netdev);
    struct bnad_iocmd_comp fcomp;
    const struct firmware *fw;
    int ret = 0;
    ret = request_firmware(&fw, eflash->data, &bnad->pcidev->dev);
    if (ret) {
        netdev_err(netdev, "can't load firmware %s\n", eflash->data);
        goto out;
    }
    fcomp.bnad = bnad;
    fcomp.comp_status = 0;
    init_completion(&fcomp.comp);
    spin_lock_irq(&bnad->bna_lock);
    ret = bfa_nw_flash_update_part(&bnad->bna.flash, BFA_FLASH_PART_FWIMG,
                bnad->id, (u8 *)fw->data, fw->size, 0,
                bnad_cb_completion, &fcomp);
    if (ret != BFA_STATUS_OK) {
        netdev_warn(netdev, "flash update failed with err=%d\n", ret);
        ret = -EIO;
        spin_unlock_irq(&bnad->bna_lock);
        goto out;
    }
    spin_unlock_irq(&bnad->bna_lock);
    wait_for_completion(&fcomp.comp);
    if (fcomp.comp_status != BFA_STATUS_OK) {
        ret = -EIO;
        netdev_warn(netdev,
                "firmware image update failed with err=%d\n",
                fcomp.comp_status);
    }
out:
    release_firmware(fw);
    return ret;
}

/**
 * bfa_nw_flash_update_part - Update flash partition.
 *
 * @flash: flash structure
 * @type: flash partition type
 * @instance: flash partition instance
 * @buf: update data buffer
 * @len: data buffer length
 * @offset: offset relative to the partition starting address
 * @cbfn: callback function
 * @cbarg: callback argument
 *
 * Return status.
 */
enum bfa_status
bfa_nw_flash_update_part(struct bfa_flash *flash, u32 type, u8 instance,
             void *buf, u32 len, u32 offset,
             bfa_cb_flash cbfn, void *cbarg)
{
    if (!bfa_nw_ioc_is_operational(flash->ioc))
        return BFA_STATUS_IOC_NON_OP;

    /*
     * 'len' must be in word (4-byte) boundary
     */
    if (!len || (len & 0x03))
        return BFA_STATUS_FLASH_BAD_LEN;

    if (type == BFA_FLASH_PART_MFG)
        return BFA_STATUS_EINVAL;

    if (flash->op_busy)
        return BFA_STATUS_DEVBUSY;

    flash->op_busy = 1;
    flash->cbfn = cbfn;
    flash->cbarg = cbarg;
    flash->type = type;
    flash->instance = instance;
    flash->residue = len;
    flash->offset = 0;
    flash->addr_off = offset;
    flash->ubuf = buf;

    bfa_flash_write_send(flash);

    return BFA_STATUS_OK;
}

/*
 * Send flash write request.
 */
static void
bfa_flash_write_send(struct bfa_flash *flash)
{
    struct bfi_flash_write_req *msg =
            (struct bfi_flash_write_req *) flash->mb.msg;
    u32 len;

    msg->type = be32_to_cpu(flash->type);
    msg->instance = flash->instance;
    msg->offset = be32_to_cpu(flash->addr_off + flash->offset);
    len = (flash->residue < BFA_FLASH_DMA_BUF_SZ) ?
           flash->residue : BFA_FLASH_DMA_BUF_SZ;
    msg->length = be32_to_cpu(len);

    /* indicate if it's the last msg of the whole write operation */
    msg->last = (len == flash->residue) ? 1 : 0;

    bfi_h2i_set(msg->mh, BFI_MC_FLASH, BFI_FLASH_H2I_WRITE_REQ,
            bfa_ioc_portid(flash->ioc));
    bfa_alen_set(&msg->alen, len, flash->dbuf_pa);
    memcpy(flash->dbuf_kva, flash->ubuf + flash->offset, len);
    bfa_nw_ioc_mbox_queue(flash->ioc, &flash->mb, NULL, NULL);

    flash->residue -= len;
    flash->offset += len;
}

Reference

  1. Linux Tv: Firmware
  2. Ubuntu: Firmware
  3. Linux 内核 firmware 加载过程
  4. 14.8. Dealing with Firmware
  5. linux-x.y.z/Documentation/firmware_class/README

留言

這個網誌中的熱門文章

yocto recipe : (1) 撰寫 recipe

yocto recipe : (2) 撰寫 bbappend

yocto recipe : (3) 使用 External Source 來編譯軟體