20230126使AIO-3568J开发板在原厂Android11下跑起来
2023/1/26 18:22
1、前提
2、修改dts设备树
3、适配板子的dts
4、(修改uboot)编译系统烧入固件验证
前提
因源码是直接使用原厂的SDK,没有使用firefly配套的SDK源码,所以手上这块firefly aio-3568i板子在硬计上与原厂使用的板子会有些差异;
就是因为这些差异会导致新固件烧入后,debug串口 打印许多错息,可能的话,系统还会崩溃掉,跑不起来
主要差异在哪里呢?比如:
1、DDR类型和品牌多样性;
2、电源管理PMU: RK809 PMU芯片各路输出电压不同,需要修改dts配置
3、CPU,GPU电压配置或电压域配置问题
4、GPIO控制管脚分配控制的芯片器件不同这些问题只需我们到dts 设备树上进行修改,重新适配下我们手上的aio-3568i板子
1、【临时使用可以不用修改的!^_】修改dts设备树
找当前kernel使用的是哪个dts文件先配置Android分支:
/build/envsetup.sh
lunch rk3566_r-userdebug
查找方法:
查看编译脚本用的是哪个dts文件
查看build.sh脚本,找到kernel当前使用的dts名称:
Y:\rk3568_Android11.0_ToyBrick\device\rockchip\rk356x\rk3568_r\BoardConfig.mk
PRODUCT_KERNEL_DTS := rk3568-evb1-ddr4-v10
修改为:
PRODUCT_KERNEL_DTS := rk3568-evb2-lp4x-v10
# only save the version code
SDK_VERSION=`get_build_var CURRENT_SDK_VERSION`
UBOOT_DEFCONFIG=`get_build_var PRODUCT_UBOOT_CONFIG`
KERNEL_ARCH=`get_build_var PRODUCT_KERNEL_ARCH`
KERNEL_DEFCONFIG=`get_build_var PRODUCT_KERNEL_CONFIG`
if [ "$KERNEL_DTS" = "" ] ; then
KERNEL_DTS=`get_build_var PRODUCT_KERNEL_DTS`
fi
echo "-------------------KERNEL_DTS:$KERNEL_DTS"
PACK_TOOL_DIR=RKTools/linux/Linux_Pack_Firmware
IMAGE_PATH=rockdev/Image-$TARGET_PRODUCT
export PROJECT_TOP=`gettop`
lunch $TARGET_PRODUCT-$BUILD_VARIANT
服务器shell终端输入: get_build_var PRODUCT_KERNEL_DTS
ubuntu:~/rk356x/rk356x android11$ get build var PRODUCT KERNEL DTS68-evb1-ddr4-v10
从中可知道kernel是使用rk3568-evb1-ddr4-v10.dts
可以到源码中查找:
kernel/arch/arm64/boot/dts/rk3568-evb1-ddr4-v10.dts
替换dts文件
如如果需要替换为其他名称的dts,可以到这个文件下修改
device/rockchip/rk356x/rk3568_r/BoardConfig.mk
演示:因AIO-3568J的DDR是LPDDR4,所以替换个rk3568-evb2-p4x-v10.dts
修改: device/rockchip/rk356x/rk3568_r/BoardConfig.mk
PRODUCT_UBOOT_CONFIG := rk3568
PRODUCT_KERNEL_DTS := rk3568-evb2-lp4x-v10
BOARD_GSENSOR_MXC6655XA_SUPPORT := true
BOARD_CAMERA_SUPPORT_EXT := true
BOARD_HS_ETHERNET := true
再次执行: get build_var PRODUCT_KERNEL DTS
即可看到kernel使用的dts变为rk3568-evb2-lp4x-v10.dts
rootroot@rootroot-HP-ZHAN-66-Pro-A-14-G3:~/rk3568_Android11.0_ToyBrick$
rootroot@rootroot-HP-ZHAN-66-Pro-A-14-G3:~/rk3568_Android11.0_ToyBrick$ get_build_var PRODUCT_KERNEL_DTS
rk3568-evb2-lp4x-v10
rootroot@rootroot-HP-ZHAN-66-Pro-A-14-G3:~/rk3568_Android11.0_ToyBrick$
适配板子的dts
先打开板子的原理图和位置图
嵌入式开发和学习,板子硬件的原理图和位置图PDF图纸是不可或少的哈aio-3568i-位置图背面pdf 2021/8/6 23:56
aio-3568j-位置图正面pdf 2021/8/6 23:57
MB-JM3-RK3568-V12-20210616pdf 2021/8/7 8:25
【临时使用可以不用修改的!^_但是是给WIFI和以太网RJ45供电的,长期使用可能会损害相关器件!^_】
2、因AIO-3568J板子是核心板+底板形式,firefly官方并没有开放核心板的原理图,所以dts的配置需要参考发布的Android11源码;
主要参考:IO电源域和RK809配置部分;
可看下此篇文章IO电源域和RK809知识点:
https://blog.csdn.net/soar999999/article/details/120102934
[[RK3568 Android11] 教程之IO电源域和rk809 DTS讲解
两套SDK源码对比最终的dts配置如下:
lIO电源域配置 (rk3568-evbd.tsi) :
Y:\rk3568_Android11.0_ToyBrick\kernel\arch\arm64\boot\dts\rockchip\rk3568-evb.dtsi
/*
* There are 10 independent IO domains in RK3566/RK3568, including PMUIO[0:2] and VCCIO[1:7].
* 1/ PMUIO0 and PMUIO1 are fixed-level power domains which cannot be configured;
* 2/ PMUIO2 and VCCIO1,VCCIO[3:7] domains require that their hardware power supply voltages
* must be consistent with the software configuration correspondingly
* a/ When the hardware IO level is connected to 1.8V, the software voltage configuration
* should also be configured to 1.8V accordingly;
* b/ When the hardware IO level is connected to 3.3V, the software voltage configuration
* should also be configured to 3.3V accordingly;
* 3/ VCCIO2 voltage control selection (0xFDC20140)
* BIT[0]: 0x0: from GPIO_0A7 (default)
* BIT[0]: 0x1: from GRF
* Default is determined by Pin FLASH_VOL_SEL/GPIO0_A7:
* L:VCCIO2 must supply 3.3V
* H:VCCIO2 must supply 1.8V
*/
&pmu_io_domains {
status = "okay";
pmuio1-supply = <&vcc3v3_pmu>;
pmuio2-supply = <&vcc3v3_pmu>;
vccio1-supply = <&vccio_acodec>;
vccio3-supply = <&vccio_sd>;
vccio4-supply = <&vcc_1v8>;
vccio5-supply = <&vcc_3v3>;
vccio6-supply = <&vcc_1v8>;
vccio7-supply = <&vcc_3v3>;
};
&pwm4 {
status = "okay";
};
【以下完全相同:就不上源码了!】
RK809各路输出电压配置 (rk3568-evb.dtsi) :
P:\AIO-3568J\rk3568_Android11.0_ToyBrick\kernel\arch\arm64\boot\dts\rockchip\rk3568-firefly-core.dtsi
3、【经过确认:可以不用修改。虽然Firefly的rbin的版本比ToyBrick的新!^_】
编译系统烧入固件验证
DTS里IO电源域和RK809都配置好后,编译固件,烧录到板子上验证烧录后,AIO-3568J板子会大概率崩溃重启,偶尔能进入Android系统桌面
用使比较软件对比了原厂的SDK包和firefly发布的SDK包,uboot和kernel部分没看出哪里有区别;然后对firefly固件和原厂SDK编译出来的固件开机log; 发现使用的DDR配置版本不同,firely使用的的版本比较新,所以尝试更新下DDR版本:路径:
sdk/rkbin
比较目录后,果真是有区别;
解决办法: 把firefly的rkbin目录有差异的地方都拷贝过来;
重新编译固件: ./build.sh -UKAu
修改过后,AIO-3568J板子每次开机都能稳定的运行,没有出现崩溃和重启现象;老化了一段时间,系统正常;
Y:\rk3568_Android11.0_ToyBrick\rkbin\RKBOOT\RK3566MINIALL.ini
[CHIP_NAME]
NAME=RK3568
[VERSION]
MAJOR=1
MINOR=1
[CODE471_OPTION]
NUM=1
Path1=bin/rk35/rk3566_ddr_1056MHz_v1.08.bin
Sleep=1
[CODE472_OPTION]
NUM=1
Path1=bin/rk35/rk356x_usbplug_v1.09.bin
[LOADER_OPTION]
NUM=2
LOADER1=FlashData
LOADER2=FlashBoot
FlashData=bin/rk35/rk3566_ddr_1056MHz_v1.08.bin
FlashBoot=bin/rk35/rk356x_spl_v1.11.bin
[OUTPUT]
PATH=rk356x_spl_loader_v1.08.111.bin
[SYSTEM]
NEWIDB=true
[FLAG]
471_RC4_OFF=true
RC4_OFF=true
新版本:
[CHIP_NAME]
NAME=RK3568
[VERSION]
MAJOR=1
MINOR=1
[CODE471_OPTION]
NUM=1
Path1=bin/rk35/rk3566_ddr_1056MHz_v1.09.bin
Sleep=1
[CODE472_OPTION]
NUM=1
Path1=bin/rk35/rk356x_usbplug_v1.10.bin
[LOADER_OPTION]
NUM=2
LOADER1=FlashData
LOADER2=FlashBoot
FlashData=bin/rk35/rk3566_ddr_1056MHz_v1.09.bin
FlashBoot=bin/rk35/rk356x_spl_v1.11.bin
[OUTPUT]
PATH=rk356x_spl_loader_v1.09.111.bin
[SYSTEM]
NEWIDB=true
[FLAG]
471_RC4_OFF=true
RC4_OFF=true
4、替换uboot的三个文件,重新编译uboot。刷机就可以进去了!
Y:\rk3568_Android11.0_ToyBrick\u-boot\arch\arm\mach-rockchip\board.c
#include
#ifdef CONFIG_DM_CHARGE_DISPLAY
#include
#endif
#ifdef CONFIG_DM_DVFS
#include
#endif
#ifdef CONFIG_ROCKCHIP_IO_DOMAIN
#include
#endif
#ifdef CONFIG_DM_REGULATOR
#include
#endif
#ifdef CONFIG_DRM_ROCKCHIP
#include
#endif
#ifdef CONFIG_ROCKCHIP_DEBUGGER
#include
#endif
#include
#include
#include
#include
#define BLK_OFFSET_31K 62
#define ABNORMAL_BOOT_COUNT 2
static long abnormal_boot_detect(void)
{
#ifdef CONFIG_ABNORMAL_BOOT_DETECT
long ret;
char *buf;
struct blk_desc *dev_desc;
char flag;
dev_desc = rockchip_get_bootdev();
lbaint_t start = BLK_OFFSET_31K;
buf = (char *)memalign(ARCH_DMA_MINALIGN, RK_BLK_SIZE);
if(!buf) {
printf("%s: out of memory!\n", __func__);
return -ENOMEM;
}
ret = blk_dread(dev_desc, start, 1, buf);
if(ret < 0) {
printf("%s: failed to get abnormal boot flag, ret=%lu\n",
__func__, ret);
return ret;
}
flag = buf[0];
if(flag < ABNORMAL_BOOT_COUNT) {
printf("Abnormal boot count: %d\n", flag);
flag++;
buf[0] = flag;
ret = blk_dwrite(dev_desc, start, 1, buf);
} else {
buf[0] = 0;
ret = blk_dwrite(dev_desc, start, 1, buf);
printf("Enter bootrom download...");
mdelay(100);
writel(BOOT_BROM_DOWNLOAD, CONFIG_ROCKCHIP_BOOT_MODE_REG);
do_reset(NULL, 0, 0, NULL);
printf("failed!\n");
}
return ret;
#else
return 0;
#endif
}
static void board_debug_init(void)
{
if (!gd->serial.using_pre_serial &&
!(gd->flags & GD_FLG_DISABLE_CONSOLE))
debug_uart_init();
if (tstc()) {
gd->console_evt = getc();
if (gd->console_evt <= 0x1a) /* 'z' */
printf("Hotkey: ctrl+%c\n", gd->console_evt + 'a' - 1);
}
if (IS_ENABLED(CONFIG_CONSOLE_DISABLE_CLI))
printf("Cmd interface: disabled\n");
}
#ifdef CONFIG_MTD_BLK
static void board_mtd_blk_map_partitions(void)
{
struct blk_desc *dev_desc;
dev_desc = rockchip_get_bootdev();
if (dev_desc)
mtd_blk_map_partitions(dev_desc);
}
#endif
int board_init(void)
{
board_debug_init();
abnormal_boot_detect();
#ifdef DEBUG
soc_clk_dump();
#endif
Y:\rk3568_Android11.0_ToyBrick\u-boot\arch\arm\mach-rockchip\boot_mode.c
#include
#include
#include
static int load_SnMacAc_from_vendor(char *sn, char *mac, char *actcode)
{
int ret;
memset(sn, 0, TOYBRICK_SN_LEN + 1);
memset(mac, 0, TOYBRICK_MAC_LEN + 1);
memset(actcode, 0, TOYBRICK_ACTCODE_LEN + 1);
ret = toybrick_get_sn(sn);
if (ret <= 0) {
printf("Load sn form vendor failed\n");
return -EIO;
}
ret = toybrick_get_mac(mac);
if (ret != TOYBRICK_MAC_LEN) {
printf("Load mac form vendor failed\n");
return -EIO;
}
ret = toybrick_get_actcode(actcode);
if (ret != TOYBRICK_ACTCODE_LEN) {
printf("Load actcode form vendor failed\n");
return -EIO;
}
printf("Load SnMacAc from vendor: sn %s, mac %2.2x%2.2x%2.2x%2.2x%2.2x%2.2x\n",
sn, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return 0;
}
static int save_SnMacAc_to_vendor(char *sn, char *mac, char *actcode)
{
int ret;
ret = toybrick_set_sn(sn);
if (ret <= 0) {
printf("Save sn to vendor failed\n");
return -EIO;
}
ret = toybrick_set_mac(mac);
if (ret != TOYBRICK_MAC_LEN) {
printf("Save mac to vendor failed\n");
return -EIO;
}
ret = toybrick_set_actcode(actcode);
if (ret != TOYBRICK_ACTCODE_LEN) {
printf("Save actcode to vendor failed\n");
return -EIO;
}
return 0;
}
static int load_SnMacAc_from_rpmb(char *sn, char *mac, char *actcode)
{
int ret;
sha256_context ctx;
uint8_t digest[SHA256_SUM_LEN + 1] = {0};
uint8_t hash_pre[SHA256_SUM_LEN + 1] = {0};
uint8_t data_sha256[TOYBRICK_SHA_LEN + 1]={0};
memset(sn, 0, TOYBRICK_SN_LEN + 1);
memset(mac, 0, TOYBRICK_MAC_LEN + 1);
memset(actcode, 0, TOYBRICK_ACTCODE_LEN + 1);
ret = trusty_read_toybrick_SnMacAc(data_sha256, TOYBRICK_SHA_LEN);
if (ret != 0) {
printf("Load SnMacAc from rpmb failed\n");
return -EIO;
}
memcpy(hash_pre, data_sha256, SHA256_SUM_LEN);
sha256_starts(&ctx);
sha256_update(&ctx,(const uint8_t *)(data_sha256 + SHA256_SUM_LEN), TOYBRICK_DATA_LEN);
sha256_finish(&ctx, digest);
if (memcmp(digest, hash_pre, SHA256_SUM_LEN) != 0) {
printf("SnMacAc from rpmb is invalid\n");
return -EINVAL;
}
memcpy(sn, data_sha256 + SHA256_SUM_LEN, TOYBRICK_SN_LEN);
memcpy(mac, data_sha256 + SHA256_SUM_LEN + TOYBRICK_SN_LEN, TOYBRICK_MAC_LEN);
memcpy(actcode, data_sha256 + SHA256_SUM_LEN + TOYBRICK_SN_LEN + TOYBRICK_MAC_LEN, TOYBRICK_ACTCODE_LEN);
if (strlen(sn) == 0) {
printf("SnMacAc from rpmb is empty\n");
return -EINVAL;
}
printf("Load SnMacAc from rpmb: sn %s, mac %2.2x%2.2x%2.2x%2.2x%2.2x%2.2x\n",
sn, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return 0;
}
static int save_SnMacAc_to_rpmb(char *sn, char *mac, char *actcode)
{
int ret;
sha256_context ctx;
uint8_t digest[SHA256_SUM_LEN + 1] = {0};
uint8_t data[TOYBRICK_DATA_LEN + 1]={0};
uint8_t data_sha256[TOYBRICK_SHA_LEN + 1]={0};
memset(&data, 0, sizeof(data));
memset(&data_sha256, 0, sizeof(data_sha256));
memcpy(data, sn, TOYBRICK_SN_LEN);
memcpy(data + TOYBRICK_SN_LEN, mac, TOYBRICK_MAC_LEN);
memcpy(data + TOYBRICK_SN_LEN + TOYBRICK_MAC_LEN, actcode, TOYBRICK_ACTCODE_LEN);
sha256_starts(&ctx);
sha256_update(&ctx,(const uint8_t *)data, TOYBRICK_DATA_LEN);
sha256_finish(&ctx, digest);
memcpy(data_sha256, digest, SHA256_SUM_LEN);
memcpy(data_sha256 + SHA256_SUM_LEN, data, TOYBRICK_DATA_LEN);
ret = trusty_write_toybrick_SnMacAc(data_sha256, TOYBRICK_SHA_LEN);
if (ret != 0) {
printf("Save SnMacAc to rpmb failed\n");
return -EIO;
}
return 0;
}
static int toybrick_check_SnMacAc(void)
{
int ret = 0;
int ret_vendor, ret_rpmb;
char vendor_sn[TOYBRICK_SN_LEN + 1];
char vendor_mac[TOYBRICK_MAC_LEN + 1];
char vendor_actcode[TOYBRICK_ACTCODE_LEN + 1];
char rpmb_sn[TOYBRICK_SN_LEN + 1];
char rpmb_mac[TOYBRICK_MAC_LEN + 1];
char rpmb_actcode[TOYBRICK_ACTCODE_LEN + 1];
ret_vendor = load_SnMacAc_from_vendor(vendor_sn, vendor_mac, vendor_actcode);
ret_rpmb = load_SnMacAc_from_rpmb(rpmb_sn, rpmb_mac, rpmb_actcode);
if (ret_vendor < 0 && ret_rpmb < 0) {
printf("No SnMacAc found in vendor and rpmb, goto loader ...\n");
run_command_list("rockusb 0 ${devtype} ${devnum}", -1, 0);
//set_back_to_bootrom_dnl_flag();
do_reset(NULL, 0, 0, NULL);
} else if (ret_vendor < 0) {
printf("No SnMacAc found in vendor, load from rpmb and save to vendor\n");
ret = save_SnMacAc_to_vendor(rpmb_sn, rpmb_mac, rpmb_actcode);
do_reset(NULL, 0, 0, NULL);
} else if (ret_rpmb < 0) {
printf("No SnMacAc found in rpmb, load from vendor and save to rpmb\n");
ret = save_SnMacAc_to_rpmb(vendor_sn, vendor_mac, vendor_actcode);
} else if (memcmp(vendor_sn, rpmb_sn, TOYBRICK_SN_LEN) != 0){
printf("Warn: SN(%s %s) form vendor and rpmb is different!\n",
vendor_sn, rpmb_sn);
ret = save_SnMacAc_to_vendor(rpmb_sn, rpmb_mac, rpmb_actcode);
do_reset(NULL, 0, 0, NULL);
} else if (memcmp(vendor_mac, rpmb_mac, TOYBRICK_MAC_LEN) != 0){
printf("Warn: MAC form vendor and rpmb is different!\n");
ret = save_SnMacAc_to_vendor(rpmb_sn, rpmb_mac, rpmb_actcode);
do_reset(NULL, 0, 0, NULL);
} else if (memcmp(vendor_actcode, rpmb_actcode, TOYBRICK_ACTCODE_LEN) != 0){
printf("Warn: Actcode form vendor and rpmb is different!\n");
ret = save_SnMacAc_to_vendor(rpmb_sn, rpmb_mac, rpmb_actcode);
do_reset(NULL, 0, 0, NULL);
} else {
printf("Toybrick check SnMacAc OK, sn %s\n", vendor_sn);
ret = 0;
}
return ret;
}
int setup_boot_mode(void)
{
char env_preboot[256] = {0};
#ifndef CONFIG_ROCKCHIP_RK3288
toybrick_check_SnMacAc();
#endif
switch (rockchip_get_boot_mode()) {
case BOOT_MODE_BOOTLOADER:
printf("enter fastboot!\n");
#if defined(CONFIG_FASTBOOT_FLASH_MMC_DEV)
snprintf(env_preboot, 256,
"setenv preboot; mmc dev %x; fastboot usb 0; ",
CONFIG_FASTBOOT_FLASH_MMC_DEV);
#elif defined(CONFIG_FASTBOOT_FLASH_NAND_DEV)
snprintf(env_preboot, 256,
"setenv preboot; fastboot usb 0; ");
#endif
Y:\rk3568_Android11.0_ToyBrick\u-boot\arch\arm\mach-rockchip\boot_rkimg.c
#include
#include
#include
#define KEY_DOWN_MIN_VAL 0
#define KEY_DOWN_MAX_VAL 30
__weak int rockchip_dnl_key_pressed(void)
{
#if defined(CONFIG_DM_KEY)
#ifdef CONFIG_CMD_ROCKUSB
return key_is_pressed(key_read(KEY_VOLUMEUP));
#else
return key_is_pressed(key_read(KEY_MENU));
#endif
#elif defined(CONFIG_ADC)
const void *blob = gd->fdt_blob;
int node, ret, channel = 1;
u32 val, chns[2];
node = fdt_node_offset_by_compatible(blob, 0, "adc-keys");
if (node >= 0) {
if (!fdtdec_get_int_array(blob, node, "io-channels", chns, 2))
channel = chns[1];
}
ret = adc_channel_single_shot("saradc", channel, &val);
if (ret) {
printf("%s: Failed to read saradc, ret=%d\n", __func__, ret);
return 0;
}
return ((val >= KEY_DOWN_MIN_VAL) && (val <= KEY_DOWN_MAX_VAL));
#endif
return 0;
}
#if defined(CONFIG_ROCKCHIP_EARLY_DISTRO_DTB)
static int rockchip_read_distro_dtb(void *fdt_addr)
{
const char *cmd = "part list ${devtype} ${devnum} -bootable devplist";
char *devnum, *devtype, *devplist;
char devnum_part[12];
char fdt_hex_str[19];
char *fs_argv[5];
char flag[TOYBRICK_FLAG_LEN + 1];
char dtb_path[128];
int index = -1;
//int size;
//int ret;
if (!rockchip_get_bootdev() || !fdt_addr)
return -ENODEV;
if (run_command_list(cmd, -1, 0)) {
printf("Failed to find -bootable\n");
return -EINVAL;
}
devplist = env_get("devplist");
if (!devplist)
return -ENODEV;
devtype = env_get("devtype");
devnum = env_get("devnum");
sprintf(devnum_part, "%s:%s", devnum, devplist);
sprintf(fdt_hex_str, "0x%lx", (ulong)fdt_addr);
fs_argv[0] = "load";
fs_argv[1] = devtype,
fs_argv[2] = devnum_part;
fs_argv[3] = fdt_hex_str;
if(toybrick_get_flag(flag, &index) < 0)
strcpy(dtb_path, CONFIG_ROCKCHIP_EARLY_DISTRO_DTB_PATH);
else
sprintf(dtb_path, "%s.%s", CONFIG_ROCKCHIP_EARLY_DISTRO_DTB_PATH, flag);
fs_argv[4] = dtb_path;
if (do_load(NULL, 0, 5, fs_argv, FS_TYPE_ANY))
return -EIO;
if (fdt_check_header(fdt_addr))
return -EBADF;
printf("DTB(Distro): %s\n", dtb_path);
return 0;
}
#endif
参考资料:
https://blog.csdn.net/soar999999/article/details/120102401
[RK3568 Android11] 教程之原厂SDK源码适配AIO-3568J板子跑起来
https://t.rock-chips.com/wiki.php
ToyBrick
前言
本站旨在高效地知识分享--所见即所得。
让Toybrick板用户能够快速上手,利用Toybrick系列开发板快速开发、评估以及产品化。
https://t.rock-chips.com/wiki.php?filename=%E6%9D%BF%E7%BA%A7%E6%8C%87%E5%8D%97/TB-RK3568X
TB-RK3568X
https://t.rock-chips.com/wiki.php?filename=%E8%B5%84%E6%96%99%E4%B8%8B%E8%BD%BD/%E8%B5%84%E6%96%99%E4%B8%8B%E8%BD%BD
Android源码下载
Android源码下载地址
https://eyun.baidu.com-s/3c2U4Y7A#sharelink/path=%2F