MT6739 preloaer流程分析

preloader阶段初始化流程图
这里写图片描述

代码流程图
这里写图片描述

\vendor\mediatek\proprietary\bootable\bootloader\preloader\platform\mt6739\src\init\init.S
这里写图片描述

nit.s 主要干的事情是切换系统到管理模式(svc)(如果平台有实现el3,那么pre-loader运行在el3,否则运行在el1),禁止irq/fiq,设置stack等, 然后jump到c代码main函数入口。

\vendor\mediatek\proprietary\bootable\bootloader\preloader\platform\mt6739\src\core\main.c

void main(u32 *arg)
{
    struct bldr_command_handler handler;
    u32 jump_addr, jump_arg;

    /* get the bldr argument */
    p_bldr_param = &bldr_param;
    memcpy((void *)p_bldr_param, (void *)*arg, sizeof(bl_param_t));

#ifdef MTK_SECURITY_SW_SUPPORT
    /* note that if you use cmm file, these parameters are empty. */
    seclib_set_pl_load_addr(p_bldr_param->bl_loadinfo[0].bl_load_addr);
    seclib_set_cc_status(p_bldr_param->cc_lcs, p_bldr_param->cc_flags);
    seclib_set_sctrl_info(p_bldr_param->sctrl_cert_file_addr, p_bldr_param->sctrl_cert_file_len);
    seclib_set_tool_auth_info(p_bldr_param->tool_auth_file_addr, p_bldr_param->tool_auth_file_len);
    seclib_set_me_id(p_bldr_param->meid, ME_IDENTITY_LEN);
    seclib_set_soc_id(p_bldr_param->socid, SOC_ID_LEN);
    seclib_set_prov_key(p_bldr_param->prov_key, PROVISIONING_KEY_LEN);
#endif

#if CFG_CANCEL_BWDT_TIMEOUT
    /* Cancel BWDT timeout, otherwise it would reset in 2 second. */
    DRV_WriteReg32(0x100070A4, DRV_Reg32(0x100070A4) | 0x66000001);
#endif
    //初始化串口
    mtk_uart_init(UART_SRC_CLK_FRQ, CFG_LOG_BAUDRATE);
    //该函数做了很多事情,包括了各种的平台硬件操作(timer、pll、pmic、gpio、wdt、iic……)
    bldr_pre_process();

    #ifdef HW_INIT_ONLY
#if !CFG_FPGA_PLATFORM
    /*
     * The following is requested by MD: Ying Hsu and Jim Chou
     * Set VCORE and VMODEM as 1.19375V respectively.
     * Read the settings back and print the results.
     */
    pmic_config_interface(0x152A, 0x6C, 0x7F,0);
    pmic_config_interface(0x15AA, 0x6F, 0x7F,0);

    unsigned int val = 0;
    pmic_read_interface(0x152A, &val, 0x7F,0);
    print("VCORE: %d\n", val);
    pmic_read_interface(0x15AA, &val, 0x7F,0);
    print("VMODEM: %d\n", val);
#endif

    bldr_wait_forever();
    #endif

    handler.priv = NULL;
    handler.attr = 0;
    handler.cb   = bldr_cmd_handler;

    BOOTING_TIME_PROFILING_LOG("before bldr_handshake");
    //该函数式获得启动模式等信息保存到g_boot_mode和g_boot_mode全局变量中
    bldr_handshake(&handler);
    BOOTING_TIME_PROFILING_LOG("bldr_handshake");

#if !CFG_FPGA_PLATFORM
    /* security check */
    device_APC_dom_setup();
#endif
    BOOTING_TIME_PROFILING_LOG("sec_boot_check");

#if CFG_ATF_SUPPORT
    trustzone_pre_init();
#endif

#if defined(MTK_AB_OTA_UPDATER)
    /* This is for MT6739 only.
     * Since preloader needs to load loader_ext_dram, the time of executing
     * ab_ota_boot_check() is earlier than other platform. Once g_boot_mode
     * is assigned as RECOVERY_BOOT in ab_ota_boot_check(), it would be
     * overwritten as NORMAL_BOOT in bldr_pre_process(). Therefore we back up
     * g_boot_mode and resume it later if it is RECOVERY_BOOT.
     */
    if (ab_boot_mode == RECOVERY_BOOT) {
        print("ab_boot_mode: %d\n", ab_boot_mode);
        g_boot_mode = RECOVERY_BOOT;
    }
#endif

    BOOTING_TIME_PROFILING_LOG("before load image");
#if !(CFG_BYPASS_LOAD_IMG_FORCE_ATF)
    /* Do not load ATF, lk, load by JTAG */
//bldr_load_images
此函数要做的事情就是把lk从ROM中指定位置load到DRAM中,开机log中可以看到具体信息:
[PART] load "lk" from 0x0000000001CC0200 (dev) to 0x81E00000 (mem) [SUCCESS]这里准备好了jump到DRAM的具体地址

    if (0 != bldr_load_images(&jump_addr)) {
        print("%s Second Bootloader Load Failed\n", MOD);
        goto error;
    }
#else
    jump_addr = CFG_UBOOT_MEMADDR;
#endif
    BOOTING_TIME_PROFILING_LOG("load image");
// bldr_post_process()该函数的具体实现是platform_post_init();该函数通过hw_check_battery检测电池是否存在,如果电池不存在就一直死循环,因此第一次不接电池有可能开不了机就可能是这里导致的
    bldr_post_process();
#ifdef SLT
    mt_usb_phy_recover();
    //mu3d_hal_rst_dev();
#endif

#if CFG_ATF_SUPPORT
    trustzone_post_init();
#endif

#if CFG_LOAD_SLT_AARCH64_KERNEL
if (0 == aarch64_slt_done())
{
    *(unsigned int*) AARCH64_SLT_DONE_ADDRESS = AARCH64_SLT_DONE_MAGIC;
    jump_addr = CFG_BOOTA64_MEMADDR;
    //set up slave cpu reset address
    *(unsigned int*) 0x10200040 = CFG_BOOTA64_MEMADDR; //cpu1
    *(unsigned int*) 0x10200048 = CFG_BOOTA64_MEMADDR; //cpu2
    *(unsigned int*) 0x10200050 = CFG_BOOTA64_MEMADDR; //cpu3
    *(unsigned int*) 0x10200238 = CFG_BOOTA64_MEMADDR; //cpu4
    *(unsigned int*) 0x10200240 = CFG_BOOTA64_MEMADDR; //cpu5
    *(unsigned int*) 0x10200248 = CFG_BOOTA64_MEMADDR; //cpu6
    *(unsigned int*) 0x10200250 = CFG_BOOTA64_MEMADDR; //cpu7
    print("%s Aarch64 Kernel SLT , jump to 64 bit kernel, address: 0x%x\n", MOD,jump_addr);
    bldr_jump64(jump_addr, (u32)&bootarg, sizeof(boot_arg_t));
}
#endif
//jump_arg 跳转传入lk的参数,包括boot time、mode、reason等
#if CFG_BOOT_ARGUMENT_BY_ATAG
    jump_arg = (u32)&(g_dram_buf->boottag);
#else
    jump_arg = (u32)&bootarg;
#endif


    /* 64S3,32S1,32S1 (MTK_ATF_BOOT_OPTION = 0)
     * re-loader jump to LK directly and then LK jump to kernel directly */
#if CFG_ATF_SUPPORT
    print("%s Others, jump to ATF\n", MOD);
    bldr_jump64(jump_addr, jump_arg, sizeof(boot_arg_t));
#else
// bldr_jump该函数通过执行jump系统调用从preloader跳转到lk继续执行。
    bldr_jump(jump_addr, jump_arg, sizeof(boot_arg_t));
#endif

error:
    platform_error_handler();
}

以上简单分析了preloader的main函数的基本流程,
main 函数小结:
(1)各种硬件初始化(uart、pmic、wdt、timer、mem..);
(2)获取系统启动模式等,保存在全局变量中;
(3)Security check,跟secro.img相关;
(4)如果系统已经实现el3,则进入tz初始化;
(5)获取lk加载到DRAM的地址(固定值),然后从ROM中找到lk分区的地址, 如果没找到jump_addr,则 goto error;
(6)battery check,如果没有电池就会陷入while(1);
(7)jump到lk(如果有实现el3,则会先jump到el3,然后再回到lk
下面重点来看跟电池 pmic相关的函数bldr_post_process()流程。

static void bldr_post_process(void)
{
    platform_post_init();
}

//重点看platform_post_init这个函数

void platform_post_init(void)
{
#ifdef MTK_SECURITY_SW_SUPPORT
    /*Anti rollback update*/
#ifdef MTK_SECURITY_ANTI_ROLLBACK
    u32 g_pl_otp_status = 0x0;

    g_pl_otp_status = sec_otp_ver_update();
    if (g_pl_otp_status)
        print("update fail 0x%x\n", g_pl_otp_status);
#endif
#endif

#if CFG_FUNCTION_PICACHU_SUPPORT
    start_picachu();
    etc_entry();
#endif
#if CFG_BATTERY_DETECT //是否进行电池检测
    /* normal boot to check battery exists or not */
    if (g_boot_mode == NORMAL_BOOT && !hw_check_battery() && usb_accessory_in()) {//正常开机模式下如果USB插入且电池不在位就一直死循环,等待电池接入
        print("%s Wait for battery inserted...\n", MOD);
        /* disable pmic pre-charging led */
        //remove empty function to save space pl_close_pre_chr_led();
        /* enable force charging mode */
        //remove empty function to save space pl_charging(1);
        do {
            mdelay(300);
            /* check battery exists or not */
            if (hw_check_battery())
                break;//电池在位,退出死循环
            /* kick all watchdogs */
            platform_wdt_all_kick();//看门狗喂狗
        } while(1);
        /* disable force charging mode */
        //remove empty function to save space pl_charging(0);
    }
#endif

#if !CFG_FPGA_PLATFORM
        pl_battery_init(true);//preloader阶段电池初始化相关的操作
#endif
    BOOTING_TIME_PROFILING_LOG("Battery detect");


#if CFG_MDJTAG_SWITCH
    unsigned int md_pwr_con;

    /* md0 default power on and clock on */
    /* md1 default power on and clock off */

    /* ungate md1 */
    /* rst_b = 0 */
    md_pwr_con = DRV_Reg32(0x10006280);
    md_pwr_con &= ~0x1;
    DRV_WriteReg32(0x10006280, md_pwr_con);

    /* enable clksq2 for md1 */
    DRV_WriteReg32(0x10209000, 0x00001137);
    udelay(200);
    DRV_WriteReg32(0x10209000, 0x0000113f);

    /* rst_b = 1 */
    md_pwr_con = DRV_Reg32(0x10006280);
    md_pwr_con |= 0x1;
    DRV_WriteReg32(0x10006280, md_pwr_con);

    /* switch to MD legacy JTAG */
    /* this step is not essentially required */
#endif
    BOOTING_TIME_PROFILING_LOG("MTJTAG switch");

#if CFG_MDMETA_DETECT
    if (g_boot_mode == META_BOOT || g_boot_mode == ADVMETA_BOOT) {
    /* trigger md0 to enter meta mode */
        DRV_WriteReg32(0x20000010, 0x1);
    /* trigger md1 to enter meta mode */
        DRV_WriteReg32(0x30000010, 0x1);
    } else {
    /* md0 does not enter meta mode */
        DRV_WriteReg32(0x20000010, 0x0);
    /* md1 does not enter meta mode */
        DRV_WriteReg32(0x30000010, 0x0);
    }
#endif
    BOOTING_TIME_PROFILING_LOG("MTMETA Detect");

    platform_set_boot_args();
    BOOTING_TIME_PROFILING_LOG("Boot Argu");
}

如何检测电池是否存在?

int hw_check_battery(void)
{
#ifdef MTK_DISABLE_POWER_ON_OFF_VOLTAGE_LIMITATION
    print("ignore bat check\n");
    return 1;
#else

#if CFG_EVB_PLATFORM  //这里EVB的板子跳过电池检测,需要关注该宏在哪里定义?
    print("ignore bat check\n");
    return 1;
#else
    U32 val = 0;
    U32 ret_val;

    ret_val = pmic_config_interface(PMIC_RG_LDO_TREF_EN_ADDR, 1,
                    PMIC_RG_LDO_TREF_EN_MASK,
                    PMIC_RG_LDO_TREF_EN_SHIFT);

    pmic_upmu_set_rg_baton_en(1);
    val = pmic_upmu_get_rgs_baton_undet();//读取寄存器标志位。

    if(val == 0) {
        print("battery exists\n");
        return 1;
    } else {
        print("battery doesn't exist\n");
        return 0;
    }
#endif /* !CFG_EVB_PLATFORM */
#endif /* !MTK_DISABLE_POWER_ON_OFF_VOLTAGE_LIMITATION */
}

检测电池是否存在的寄存器判断标志位:
这里写图片描述

下面再来看pl_battery_init(true)电池初始化都做了哪些事情?

void pl_battery_init(bool force_init)
{
    if (force_init == true)
        return;

    #if !CFG_EVB_PLATFORM && !CFG_FPGA_PLATFORM
    fuel_gauge_init();
    #endif
}
void fuel_gauge_init(void)
{
    int m = 0, ret;
    int b_moniter_pl_charg_bit;
    int plcharg_status = 0;
    U32 gain_cal;


    boot_vbat = get_bat_sense_volt(1);//电池电压

    pmic_read_interface(PMIC_RG_FGADC_GAINERROR_CAL_ADDR, &gain_cal, PMIC_RG_FGADC_GAINERROR_CAL_MASK, PMIC_RG_FGADC_GAINERROR_CAL_SHIFT);
    pmic_config_interface(PMIC_FG_GAIN_ADDR, gain_cal, PMIC_FG_GAIN_MASK, PMIC_FG_GAIN_SHIFT);

    /*reset HW FG */
    pmic_config_interface(PMIC_FG_CHARGE_RST_ADDR, 1, PMIC_FG_CHARGE_RST_MASK, PMIC_FG_CHARGE_RST_SHIFT);
    pmic_config_interface(PMIC_FG_TIME_RST_ADDR, 1, PMIC_FG_TIME_RST_MASK, PMIC_FG_TIME_RST_SHIFT);
    pmic_config_interface(PMIC_FG_OFFSET_RST_ADDR, 1, PMIC_FG_OFFSET_RST_MASK, PMIC_FG_OFFSET_RST_SHIFT);
    pmic_config_interface(PMIC_FG_SW_CR_ADDR, 1, PMIC_FG_SW_CR_MASK, PMIC_FG_SW_CR_SHIFT);
    udelay(100);
    pmic_config_interface(PMIC_FG_CHARGE_RST_ADDR, 0, PMIC_FG_CHARGE_RST_MASK, PMIC_FG_CHARGE_RST_SHIFT);
    pmic_config_interface(PMIC_FG_TIME_RST_ADDR, 0, PMIC_FG_TIME_RST_MASK, PMIC_FG_TIME_RST_SHIFT);
    pmic_config_interface(PMIC_FG_OFFSET_RST_ADDR, 0, PMIC_FG_OFFSET_RST_MASK, PMIC_FG_OFFSET_RST_SHIFT);
    pmic_config_interface(PMIC_FG_SW_CR_ADDR, 0, PMIC_FG_SW_CR_MASK, PMIC_FG_SW_CR_SHIFT);

    pmic_config_interface(PMIC_FG_SW_READ_PRE_ADDR, 1, PMIC_FG_SW_READ_PRE_MASK, PMIC_FG_SW_READ_PRE_SHIFT);    
    m = 0;
    while (fg_get_data_ready_status() == 0) {
        udelay(100);
        m++;
        if (m > 5) {
            print("1.PMIC_FG_LATCHDATA_ST_SHIFT = 0\r\n");
            break;
        }
    }

    pmic_config_interface(PMIC_FG_SW_CLEAR_ADDR, 1, PMIC_FG_SW_CLEAR_MASK, PMIC_FG_SW_CLEAR_SHIFT);
    pmic_config_interface(PMIC_FG_SW_READ_PRE_ADDR, 0, PMIC_FG_SW_READ_PRE_MASK, PMIC_FG_SW_READ_PRE_SHIFT);
    m = 0;
    while (fg_get_data_ready_status() != 0) {
        udelay(100);
        m++;
        if (m > 5) {
            print("2.PMIC_FG_LATCHDATA_ST_SHIFT != 0\r\n");
            break;
        }
    }

    pmic_config_interface(PMIC_FG_SW_CLEAR_ADDR, 0, PMIC_FG_SW_CLEAR_MASK, PMIC_FG_SW_CLEAR_SHIFT);


    /* if WDT reset from PL to KERNEL, b_moniter_pl_charg_bit will keep 1*/
    /* record PL charging status in bit1, mointer bit in bit2 */
    pmic_read_interface(PMIC_RG_SYSTEM_INFO_CON0_ADDR, &b_moniter_pl_charg_bit, 0x0001, 0x2);
    if (b_moniter_pl_charg_bit == 0) {
        plcharg_status = upmu_is_chr_det();
        ret = pmic_config_interface(PMIC_RG_SYSTEM_INFO_CON0_ADDR, 0x1, 0x0001, 0x2);
        ret = pmic_config_interface(PMIC_RG_SYSTEM_INFO_CON0_ADDR, plcharg_status, 0x0001, 0x1);
    }

    ret = pmic_read_interface(PMIC_RG_SYSTEM_INFO_CON0_ADDR, &b_moniter_pl_charg_bit, 0x0001, 0x2);
    ret = pmic_read_interface(PMIC_RG_SYSTEM_INFO_CON0_ADDR, &plcharg_status, 0x0001, 0x1);

    print("pl chr:%d monitor:%d plchr:%d gain:%d\r\n",upmu_is_chr_det() b_moniter_pl_charg_bit,plcharg_status,gain_cal);

    if (hw_check_battery() == 1) {
        pl_check_bat_protect_status();
    }

}

//fuel gauge初始化完了之后开始电池保护相关的操作,类似于lk中的预充电流程,电池电压低于开机电压时就先预充电。

void pl_check_bat_protect_status(void)
{
    int bat_val = 0;
    int current, chr_volt, cnt=0, i;

#if SWCHR_POWER_PATH
    bat_val = get_i_sense_volt(1);
#else
    bat_val = get_bat_sense_volt(1);
#endif

    chr_volt = get_charger_volt(1);
    print("[%s]: check VBAT=%dmV with %dmV, VCHR=%dmV ,VCHR_HV=%dmv, start charging\n",
        __func__, bat_val, BATTERY_LOWVOL_THRESOLD, chr_volt, V_CHARGER_MAX);

    while (bat_val < BATTERY_LOWVOL_THRESOLD) {
        mtk_wdt_restart();
        if (upmu_is_chr_det() == KAL_FALSE) {
            print("No Charger\n");
            break;
        }

        chr_volt = get_charger_volt(1);
        if (chr_volt > V_CHARGER_MAX) {
            print("Vchr is too high :%dmv, threshold is %dmv\n",
                chr_volt, V_CHARGER_MAX);
            break;
        }

        pchr_turn_on_charging(KAL_TRUE);//开始充电

        cnt = 0;
        for (i = 0; i < 10; i++) {
            current = get_charging_current();
            chr_volt = get_charger_volt(1);
            if (current < 100 && chr_volt < 4400) {
                cnt++;
                print("ichg=%dmA, Vchr=%d\n", current, chr_volt);
            } else {
                print("ichg=%dmA, Vchr=%d\n", current, chr_volt);
                cnt = 0;
            }
        }

        if (cnt >= 8) {
            print("ichg and Vchr is too low: %d\n", cnt);
            pchr_turn_on_charging(KAL_FALSE);
            break;
        }
        mdelay(2000);

#if SWCHR_POWER_PATH
        bat_val = get_i_sense_volt(1);
#else
        bat_val = get_bat_sense_volt(1);
#endif
        print("[%s]: check VBAT=%dmV, Vchr=%dmV, I=%dmA\n", __func__, bat_val, chr_volt, current);
    }
    print("[%s]: check VBAT=%dmV with %dmV, stop charging\n", __func__,
            bat_val, BATTERY_LOWVOL_THRESOLD);
}

综上bldr_post_process函数中跟电池相关的操作,一是检测电池是否在位,二是fuel gauge 初始化。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
# 课程背景2021年ARM又推出了ARMv9架构,系统软件架构也在悄无声息地发生变化。在这种架构中,强调的是隔离技术,包括资源的隔离、运行时隔离,特权操作系统的权限也变得越来越小…不管您是什么领域,ARM服务器、汽车电子、手机或者是其它设备终端,安全都是其中的一个重要环节。我们常说的安全一般是只网络安全/业务安全/App安全,这些安全依赖的正是操作系统安全,操作系统安全所依赖的就是安全架构技术,在该安全架构中,首当要学习的就是Trustzone/TEE技术。只有设备安全/操作系统安全/APP安全/网络安全/服务器安全整个一条链都安全了,那么你的业务才算得上的安全.不管您是做什么的,您做不做安全或虚拟化,掌握整个系统软件架构也是一件必要的事情。您只有掌握了安全架构,你才具有全局的视野,才能进行全局的软件设计,才称得上架构师。安全不仅仅是架构安全,安全还是一种生态,安全还产生一类标准。安全出现在产品声明周期的任何一个角落,它可能零碎地出现在硬件中、零碎地出现在软件代码中。如果你不了解安全,你可能无法进行优秀的产品设计,你甚至无法去正确的阅读代码。# 课程介绍(1) 讲解ARM最新硬件架构(ARM Core、ARM Architecture)、SOC架构(2) 讲解最新的Trustzone安全架构、TEE架构、Secureboot等(3) 讲解软件组件:TF-A(ATF)、optee_os、Linux Kernel、CA/TA应用程序等,及其交互模型、设计模型(4) 多系统交互代码导读、安全论证实践、CA/TA示例实践、安全业务设计实践(5) 我们学习的是全部硬件、全部系统,软硬结合,或者是大系统的软硬件融合# 课程收益1、安全不再神秘,能cover住全局2、熟悉ARM Architecture架构知识3、熟悉SOC架构知识4、熟悉主流的系统软件框架5、知道多系统之间是如何交互的,也能够进行系统级的设计6、深入了解当前的系统安全架构以及未来安全架构趋势7、熟悉基础的安全业务设计方法8、熟悉系统的启动流程、Secureboot等9、知道Linux Kernel在大系统中的位置,以及与其它系统交互的方法10、熟悉各类标准和规范11、学习资料的获取方法 # 课程大纲《Trustzone/TEE/系统安全开发速成班》 --当前48节课/19小时说明: 本视频会持续更新,购买时请以课程目录为主。本EXCEL一个月更新一次。章节编号课程时常第一章基础和简介1课程介绍 8:332ARM和SOC的架构简介60:13第二章软硬件基础3armv8/armv9基础概念26:204ARMv8/ARMv9的Trustzone技术77:565ARMv7的Trustzone技术8:376安全架构及其未来趋势(FF-A/SPM/CCA)6:417ARMv9 CCA机密计算框架底层核心原理简介20:448ARMv9 RME安全扩展详解61:299ATF Quick Start0161:2710Optee Quick Start49:3411系统软件Quick Start21:0412Secureboot原理深度讲解60:2813Android AVB的介绍26:0914TZC400详解17:5615TZC400代码导读之ATF13:4416RPMB详解30:0617RPMB代码导读之optee14:0318efuse详解12:1919Anti-Rollback的介绍11:33第三章软件架构20TEE的组件介绍67:5921TEE的RPC反向调用31:1422TEE的调度模型21:3223各类标准和规范22:17第四章软件架构(高级)24多系统之间的管理模型(ABI/标准)10:3025多系统之间的调度12:2126多系统之间的中断(不含虚拟化)61:4727多系统之间的中断(虚拟化)6:5728再谈多系统之间的调度(多核多线程)10:3129其它(内存管理/PSCI...)10:12第五章安全应用开发基础30安全应用开发基础5:3731TEE环境:qemu_V8环境的使用4:2032TEE环境:编写一个CA和TA程序11:0433TEE环境:编写漂亮的文档4:1434TEE环境:搭建阅读代码神器opengrok3:2635TEE环境(必看):使用集成好的qemu_v8镜像程序20:2936[CA/TA开发]CA到TA的通信9:1837[CA/TA开发]CA到TA的传参27:1638[CA/TA开发]TEE中的存储系统20:5739[CA/TA开发]TEE中的密码学系统简介12:3740[CA/TA开发]TEE中的密码学系统简介-数字摘要Hash等33:2141[CA/TA开发]TEE中的密码学系统简介-对称密码学算法AES等12:2042[CA/TA开发]TEE中的密码学系统简介-消息摘要算法HMAC等15:4843[CA/TA开发]TEE中的密码学系统简介-非对称密码学算法RSA等7:33第六章安全业务设计高级44Gatekeeper的介绍29:3245keymaster/keymint/keystore/keystore213:2746生物认证(指纹/人脸)13:5747DRM的介绍21:3748TUI的介绍17:03总计时统计(分钟)1128:12 说明:本课程会持续更新…
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值