Jimmy Chen

A Programmer

(原创)高通aboot显示logo流程

  今天继续分析分析源码,看看高通8909的aboot是如何显示logo的。既然涉及到aboot,那么我们就从aboot的入口函数aboot_init开始分析。

aboot_init

void aboot_init(const struct app_descriptor *app)
{
    .......... // 前面无关代码略过

    // 从devinfo分区读取设备信息,在后面初始化panel的时候要用到
    read_device_info(&device);
    read_allow_oem_unlock(&device);

    .......... // 略过

    // 按条件设置display panel
#if DISPLAY_SPLASH_SCREEN
#if NO_ALARM_DISPLAY
    if (!check_alarm_boot()) {
#endif
        dprintf(SPEW, "Display Init: Start\n");
#if DISPLAY_HDMI_PRIMARY
    if (!strlen(device.display_panel))
        strlcpy(device.display_panel, DISPLAY_PANEL_HDMI,
            sizeof(device.display_panel));
#endif

// ENABLE_WBC只在8996的芯片上才设置为true,所以这里我们走#else
#if ENABLE_WBC
        /* Wait if the display shutdown is in progress */
        while(pm_app_display_shutdown_in_prgs());
        if (!pm_appsbl_display_init_done())
            target_display_init(device.display_panel);
        else
            display_image_on_screen();
#else
        target_display_init(device.display_panel);
#endif
        dprintf(SPEW, "Display Init: Done\n");
#if NO_ALARM_DISPLAY
    }
#endif
#endif

    .............. // 后面代码略过

aboot_init —> target_display_init

target_display_init函数在lk/target/msm8909文件夹内

void target_display_init(const char *panel_name)
{
    uint32_t panel_loop = 0;
    uint32_t ret = 0;
    struct oem_panel_data oem;

    // 根据devinfo中的panel信息,选择对应panel
    set_panel_cmd_string(panel_name);
    oem = mdss_dsi_get_oem_data();
    dprintf(INFO, "target_display_init  panel name:%s\n",
            oem.panel);

    if (!strcmp(oem.panel, NO_PANEL_CONFIG)
        || !strcmp(oem.panel, SIM_VIDEO_PANEL)
        || !strcmp(oem.panel, SIM_CMD_PANEL)
        || oem.skip) {
        dprintf(INFO, "Selected %s: Skip panel configuration\n",
                oem.panel);
        return;
    }

    // target_splash_disable返回false
    if (target_splash_disable())
        return;

    do {
        // 设置splash_override为false
        target_force_cont_splash_disable(false);
        // 重要函数,下面分析
        ret = gcdb_display_init(oem.panel, MDP_REV_305, MIPI_FB_ADDR);
        if (!ret || ret == ERR_NOT_SUPPORTED) {
            break;
        } else {
            target_force_cont_splash_disable(true);
            msm_display_off();
        }
    } while (++panel_loop <= oem_panel_max_auto_detect_panels());

    if (!oem.cont_splash) {
        dprintf(INFO, "Forcing continuous splash disable\n");
        target_force_cont_splash_disable(true);
    }
}

target_display_init ---> gcdb_display_init

int gcdb_display_init(const char *panel_name, uint32_t rev, void *base)
{
    int ret = NO_ERROR;
    int pan_type;

    dsi_video_mode_phy_db.pll_type = DSI_PLL_TYPE_28NM;
    pan_type = oem_panel_select(panel_name, &panelstruct, &(panel.panel_info),
                 &dsi_video_mode_phy_db);

    // 根据panel的不同,设置对应参数,这里就不贴出来了
    if (pan_type == PANEL_TYPE_DSI) {
        ................
    } else if (pan_type == PANEL_TYPE_EDP) {
        .................
    } else {
        dprintf(CRITICAL, "Target panel init not found!\n");
        ret = ERR_NOT_SUPPORTED;
        goto error_gcdb_display_init;
    }

    panel.fb.base = base;
    panel.mdp_rev = rev;

    // 下面重点分析
    ret = msm_display_init(&panel);

error_gcdb_display_init:
    display_enable = ret ? 0 : 1;
    return ret;
}

gcdb_display_init ---> msm_display_init

int msm_display_init(struct msm_fb_panel_data *pdata)
{
    int ret = NO_ERROR;

    panel = pdata;
    if (!panel) {
        ret = ERR_INVALID_ARGS;
        goto msm_display_init_out;
    }

    // 这部分的代码是根据之前设置的参数点亮屏幕
    if (pdata->power_func)
        ret = pdata->power_func(1, &(panel->panel_info));

    if (ret)
        goto msm_display_init_out;

    if (pdata->dfps_func)
        ret = pdata->dfps_func(&(panel->panel_info));

    /* Enable clock */
    if (pdata->clk_func)
        ret = pdata->clk_func(1, &(panel->panel_info));

    if (ret)
        goto msm_display_init_out;

    /* Read specifications from panel if available.
     * If further clocks should be enabled, they can be enabled
     * using pll_clk_func
     */
    if (pdata->update_panel_info)
        ret = pdata->update_panel_info();

    if (ret)
        goto msm_display_init_out;

    /* Enabled for auto PLL calculation or to enable
     * additional clocks
     */
    if (pdata->pll_clk_func)
        ret = pdata->pll_clk_func(1, &(panel->panel_info));

    if (ret)
        goto msm_display_init_out;

    /* pinfo prepare  */
    if (pdata->panel_info.prepare) {
        /* this is for edp which pinfo derived from edid */
        ret = pdata->panel_info.prepare();
        panel->fb.width =  panel->panel_info.xres;
        panel->fb.height =  panel->panel_info.yres;
        panel->fb.stride =  panel->panel_info.xres;
        panel->fb.bpp =  panel->panel_info.bpp;
    }

    if (ret)
        goto msm_display_init_out;

    ret = msm_fb_alloc(&(panel->fb));
    if (ret)
        goto msm_display_init_out;

    // 设置屏幕Framebuffer显示模式,RGB555 or RGB888
    fbcon_setup(&(panel->fb));
    // 下面接着分析
    display_image_on_screen();

    ................... 
}

msm_display_init ---> display_image_on_screen

void display_image_on_screen(void)
{
// DISPLAY_TYPE_MIPI为true
#if DISPLAY_TYPE_MIPI
    // 函数声明,fetch_image_from_partition定义在aboot.c中
    int fetch_image_from_partition();

    // 根据fetch_image_from_partition返回值分两种情况
    if (fetch_image_from_partition() < 0) {
        display_default_image_on_screen();
    } else {
        /* data has been put into the right place */
        fbcon_flush();
    }
#else
    display_default_image_on_screen();
#endif
}

display_image_on_screen ---> fetch_image_from_partition

int fetch_image_from_partition()
{
    // 使用emmc,走true分支
    if (target_is_emmc_boot()) {
        return splash_screen_mmc();
    } else {
        return splash_screen_flash();
    }
}

fetch_image_from_partition ---> splash_screen_mmc

int splash_screen_mmc()
{
    int index = INVALID_PTN;
    unsigned long long ptn = 0;
    struct fbcon_config *fb_display = NULL;
    struct logo_img_header *header;
    uint32_t blocksize, realsize, readsize;
    uint8_t *base;

    // 获取splash分区的index
    index = partition_get_index("splash");
    if (index == 0) {
        dprintf(CRITICAL, "ERROR: splash Partition table not found\n");
        return -1;
    }

    // 获取分区偏移
    ptn = partition_get_offset(index);
    if (ptn == 0) {
        dprintf(CRITICAL, "ERROR: splash Partition invalid\n");
        return -1;
    }

    // 设置读取的lun
    mmc_set_lun(partition_get_lun(index));

    // 获取emmc的块大小
    blocksize = mmc_get_device_blocksize();
    if (blocksize == 0) {
        dprintf(CRITICAL, "ERROR:splash Partition invalid blocksize\n");
        return -1;
    }

    // 获取framebuffer配置
    fb_display = fbcon_display();
    if (!fb_display)
    {
        dprintf(CRITICAL, "ERROR: fb config is not allocated\n");
        return -1;
    }

    // framebuffer内存地址
    base = (uint8_t *) fb_display->base;

    // 读取splash分区头信息到framebuffer中
    if (mmc_read(ptn, (uint32_t *)(base + LOGO_IMG_OFFSET), blocksize)) {
        dprintf(CRITICAL, "ERROR: Cannot read splash image header\n");
        return -1;
    }

    // 判断splash分区头信息是否有误
    header = (struct logo_img_header *)(base + LOGO_IMG_OFFSET);
    if (splash_screen_check_header(header)) {
        dprintf(CRITICAL, "ERROR: Splash image header invalid\n");
        return -1;
    }

    if (fb_display) {
        // 判断logo的格式,我们在使用logo_gen.py脚本制作splash.img时就有做RLE压缩
        // 所以这里选择第一种情况
        if (header->type && (header->blocks != 0)) { /* 1 RLE24 compressed data */
            base += LOGO_IMG_OFFSET;

            // logo真实大小
            realsize =  header->blocks * 512;
            readsize =  ROUNDUP((realsize + LOGO_IMG_HEADER_SIZE), blocksize) - blocksize;

            /* if the logo is not full-screen size, clean screen */
            if ((header->width != fb_display->width)
                        || (header->height != fb_display->height))
                fbcon_clear();

            uint32_t fb_size = ROUNDUP(fb_display->width *
                    fb_display->height *
                    (fb_display->bpp / 8), 4096);

            // logo大小过大
            if (readsize > fb_size) {
                dprintf(CRITICAL, "ERROR: Splash image size invalid\n");
                return -1;
            }

            // 读取到framebuffer中
            if (mmc_read(ptn + blocksize, (uint32_t *)(base + blocksize), readsize)) {
                dprintf(CRITICAL, "ERROR: Cannot read splash image from partition\n");
                return -1;
            }

            // 解压
            fbcon_extract_to_screen(header, (base + LOGO_IMG_HEADER_SIZE));
        } else { /* 2 Raw BGR data */
            ...................  // 省略
        }
    }

    // 最后返回0
    return 0;
}

所以只有成功读取到splash分区,并且分区中的logo的信息没有错误,最后才会返回0,否则都会返回-1。下面我们返回display_image_on_screen看看

void display_image_on_screen(void)
{
// DISPLAY_TYPE_MIPI为true
#if DISPLAY_TYPE_MIPI
    // 函数声明,fetch_image_from_partition定义在aboot.c中
    int fetch_image_from_partition();

    // 根据fetch_image_from_partition返回值分两种情况
    if (fetch_image_from_partition() < 0) {
        // 要是读取splash分区出错,我们就走default流程
        display_default_image_on_screen();
    } else {
        // 所以,如果上面读取splash分区无误,我们就显示到屏幕上
        fbcon_flush();
    }
#else
    display_default_image_on_screen();
#endif
}

下面我们看看default模式显示logo是怎么个处理法。

display_image_on_screen ---> display_default_image_on_screen

void display_default_image_on_screen(void)
{
    unsigned i = 0;
    unsigned total_x;
    unsigned total_y;
    unsigned bytes_per_bpp;
    unsigned image_base;
#if DISPLAY_TYPE_MIPI
    char *image = NULL;
#endif

    // 找不到framebuffer配置
    if (!config) {
        dprintf(CRITICAL,"NULL configuration, image cannot be displayed\n");
        return;
    }

    // 清屏
    fbcon_clear(); 

    total_x = config->width;
    total_y = config->height;
    bytes_per_bpp = ((config->bpp) / 8);
    image_base = ((((total_y/2) - (SPLASH_IMAGE_HEIGHT / 2) - 1) *
            (config->width)) + (total_x/2 - (SPLASH_IMAGE_WIDTH / 2)));

#if DISPLAY_TYPE_MIPI
#if ENABLE_WBC
    image = (pm_appsbl_charging_in_progress() ? image_batt888 : imageBuffer_rgb888);
#else
    // 走这里,将image内容设置为imageBuffer_rgb888
    // imageBuffer_rgb888是一个大数组,记录logo的信息
    image = imageBuffer_rgb888;
#endif  // end ENABLE_WBC

    if (bytes_per_bpp == 3) {
        // 直接将imageBuffer_rgb888数组的内容拷贝到framebuffer中
        for (i = 0; i < SPLASH_IMAGE_HEIGHT; i++) {
            memcpy (config->base + ((image_base + (i * (config->width))) * bytes_per_bpp),
            image + (i * SPLASH_IMAGE_WIDTH * bytes_per_bpp),
            SPLASH_IMAGE_WIDTH * bytes_per_bpp);
        }
    }
    fbcon_flush();
#if DISPLAY_MIPI_PANEL_NOVATEK_BLUE
    if(is_cmd_mode_enabled())
        mipi_dsi_cmd_mode_trigger();
#endif // end DISPLAY_MIPI_PANEL_NOVATEK_BLUE

#else  // else DISPLAY_TYPE_MIPI

    if (bytes_per_bpp == 2) {
        for (i = 0; i < SPLASH_IMAGE_HEIGHT; i++) {
            memcpy (config->base + ((image_base + (i * (config->width))) * bytes_per_bpp),
            imageBuffer + (i * SPLASH_IMAGE_WIDTH * bytes_per_bpp),
            SPLASH_IMAGE_WIDTH * bytes_per_bpp);
        }
    }
    fbcon_flush();
#endif
}

到此,显示logo的内容就完了。总结来说,显示logo有两种方法,第一种就是添加一个splash分区,将logo制作成splash.img镜像,然后刷到splash分区;另一种就是讲logo转换并填充到imageBuffer_rgb888数组即可。了解了原理,下面我们就来实践一下吧。高通MSM8909的代码都没有实现,需要自己实现。

方法一:转换成imageBuffer_rgb888数组

  好吧,这种方法当然是可以的。但是试了几次之后发现,这种方法只适合分辨率较小的logo图,博主试过720x1280、480x800和200x320这三个,发现就只有200x320可以显示成功,所以这种方法并不适合分辨率较高的logo

首先是准备好自己的logo图像,这里假设图像文件名为logo.png,依次执行下面三部得到素组

  • convert logo.png -recolor "0 0 1,0 1 0,1 0 0" logo_re.png
  • convert -depth 8 logo_re.png rgb:logo_raw.raw
  • xxd -i logo_raw.raw > splash_test.h

这里得到的splash_test.h文件内就包含我们需要的logo数组了。我们只需将里面的数组替换到lk/platform/msm_share/splash.h中的imageBuffer_rgb888数组中即可,当然要记得修改SPLASH_IMAGE_WIDTH和SPLASH_IMAGE_HEIGHT这两个宏为图像实际分辨率。

方法二:使用splash.img

首先在device/qcom/common/generate_extra_images文件中添加splash.img的编译选项,添加的内容如下:

#----------------------------------------------------------------------
# Generate splash image (splash.img)
#----------------------------------------------------------------------
INSTALLED_SPLASHIMAGE_TARGET := $(PRODUCT_OUT)/splash.img

define build-splashimage-target
    $(hide) python device/qcom/common/display/logo/logo_gen.py device/qcom/msm8909test/logo.png
    $(hide) cp splash.img $(PRODUCT_OUT)/
    $(hide) rm splash.img
endef

$(INSTALLED_SPLASHIMAGE_TARGET): 
    $(build-splashimage-target)

ALL_DEFAULT_INSTALLED_MODULES += $(INSTALLED_SPLASHIMAGE_TARGET)
ALL_MODULES.$(LOCAL_MODULE).INSTALLED += $(INSTALLED_SPLASHIMAGE_TARGET)

其次是,根据上面填写的内容,我们要见logo图像命名为logo.png,同时将logo图像放到device/qcom/msm8909test/logo.png下,各位可以根据自己项目的需要更换这个路径

最后就是修改partition.xml,默认的partition.xml文件是这样的

    <partition label="ssd" size_in_kb="8" type="2C86E742-745E-4FDD-BFD8-B6A7AC638772" bootable="false" readonly="false" filename=""/>
    <partition label="splash" size_in_kb="10240" type="20117f86-E985-4357-B9EE-374BC1D8487D" bootable="false" readonly="false" filename=""/>
    <partition label="keystore" size_in_kb="512" type="DE7D4029-0F5B-41C8-AE7E-F6C023A02B33" bootable="false" readonly="false" filename="" />

可以看出,在执行flattern的时候并不会获取hlos中编译的splash.img,所以这里我们将splash.img加上去即可。

这样使用方法二来显示lk的logo就完成了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d 博主赞过: