安卓ro.serialno產生的整個流程

来源:https://www.cnblogs.com/erdongsir/archive/2023/02/24/17152900.html
-Advertisement-
Play Games

前言: 關於ro.serialno這個屬性,相信大家都不陌生了,應用層的Build.getSerial(),Build.SERIAL等均是直接或間接的獲取了這個屬性值。接下來從boot到系統應用,小小的分析一下它的整個流程: 由於是APP經常使用,那我們從應用層分析到底層kernel/boot 一, ...


前言:

關於ro.serialno這個屬性,相信大家都不陌生了,應用層的Build.getSerial()Build.SERIAL等均是直接或間接的獲取了這個屬性值。接下來從boot到系統應用,小小的分析一下它的整個流程:

由於是APP經常使用,那我們從應用層分析到底層kernel/boot

一,framework層

好的,我們進入安卓源碼目錄,grep查找一下:

xxxx@server01:~/workspace/rk3128_tablet$ grep -nrw "SERIAL" frameworks/base/
frameworks/base/docs/html/about/versions/android-4.2.jd:364:address or the {@link android.os.Build#SERIAL} number), they will provide the same value for each
frameworks/base/api/test-current.txt:28614:    field public static final java.lang.String SERIAL;
frameworks/base/api/system-current.txt:31035:    field public static final java.lang.String SERIAL;
frameworks/base/api/current.txt:28540:    field public static final java.lang.String SERIAL;
frameworks/base/core/java/android/os/Build.java:102:    public static final String SERIAL = getString("ro.serialno");
frameworks/base/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/SinkActivity.java:61:    private static final String SERIAL = "0000000012345678";
frameworks/base/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/SinkActivity.java:254:            sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_SERIAL, SERIAL);
xxxx@server01:~/workspace/rk3128_tablet$ 

成功的在Build.java找到了這個SERIAL屬性,我們繼續往下跟getString這個方法大概在871行。

.....
/**
     * Returns the version string for the radio firmware.  May return
     * null (if, for instance, the radio is not currently on).
     */
    public static String getRadioVersion() {
        return SystemProperties.get(TelephonyProperties.PROPERTY_BASEBAND_VERSION, null);
    }

	private static String getString(String property) {
        return SystemProperties.get(property, UNKNOWN);
    }

	private static String[] getStringList(String property, String separator) {
        String value = SystemProperties.get(property);
        if (value.isEmpty()) {
            return new String[0];
        } else {
            return value.split(separator);
        }
    }
.....

SystemProperties大家應該很熟了

可以看出,getString是傳入的"ro.serialno"這個字串去獲取的屬性中的值,其效果在命令行上相當於getprop ro.serialno

好的,framework分析到這。

二,系統層

我們從第一個程式init開始,源碼路徑:

your_pro/system/core/init/init.cpp

根據關鍵字ro.serialno找到了地方,大概在464行:


static void export_kernel_boot_props() {
    char cmdline[1024];
    char* s1;
    char* s2;
    char* s3;
    char* s4;

    struct {
        const char *src_prop;
        const char *dst_prop;
        const char *default_value;
    } prop_map[] = {
        { "ro.boot.serialno",   "ro.serialno",   "", },//就是這了,根據ro.boot.serialno的值設置ro.serialno的值
        { "ro.boot.mode",       "ro.bootmode",   "unknown", },
        { "ro.boot.baseband",   "ro.baseband",   "unknown", },
        { "ro.boot.bootloader", "ro.bootloader", "unknown", },
        { "ro.boot.hardware",   "ro.hardware",   "unknown", },
        { "ro.boot.revision",   "ro.revision",   "0", },
    };

    //if storagemedia is emmc, so we will wait emmc init finish
    for (int i = 0; i < EMMC_RETRY_COUNT; i++) {
        proc_read( "/proc/cmdline", cmdline, sizeof(cmdline) );
        s1 = strstr(cmdline, STORAGE_MEDIA);
        s2 = strstr(cmdline, "androidboot.mode=emmc");
	s3 = strstr(cmdline, "storagemedia=nvme");
	s4 = strstr(cmdline, "androidboot.mode=nvme");

        if ((s1 == NULL) && (s3 == NULL)) {
            //storagemedia is unknow
            break;
        }

        if ((s1 > 0) && (s2 > 0)) {
            ERROR("OK,EMMC DRIVERS INIT OK\n");
            property_set("ro.boot.mode", "emmc");
            break;
        } else if ((s3 > 0) && (s4 > 0)) {
	    ERROR("OK,NVME DRIVERS INIT OK\n");
	    property_set("ro.boot.mode", "nvme");
	    break;
	} else {
            ERROR("OK,EMMC DRIVERS NOT READY, RERRY=%d\n", i);
            usleep(10000);
        }
    }

    for (size_t i = 0; i < ARRAY_SIZE(prop_map); i++) {//這裡這裡
        std::string value = property_get(prop_map[i].src_prop);
        property_set(prop_map[i].dst_prop, (!value.empty()) ? value.c_str() : prop_map[i].default_value);
    }

    /* save a copy for init's usage during boot */
    std::string bootmode_value = property_get("ro.bootmode");
    if (!bootmode_value.empty())
        strlcpy(bootmode, bootmode_value.c_str(), sizeof(bootmode));

    /* if this was given on kernel command line, override what we read
     * before (e.g. from /proc/cpuinfo), if anything */
    std::string hardware_value = property_get("ro.boot.hardware");
    if (!hardware_value.empty())
        strlcpy(hardware, hardware_value.c_str(), sizeof(hardware));
    property_set("ro.hardware", hardware);

    symlink_fstab();
}

以上代碼針對於ro.serialno的大致意思就是根據ro.boot.serialno的值設它。

但是,ro.boot.serialno在哪還不知道呢,我們繼續。

好的,分析開始

從mian開始,找到第一階段需要執行的代碼

int main(int argc, char** argv) {
....

	if (!is_first_stage) {
        // Indicate that booting is in progress to background fw loaders, etc.
        close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));

        property_init();

        // If arguments are passed both on the command line and in DT,
        // properties set in DT always have priority over the command-line ones.
        process_kernel_dt();
        process_kernel_cmdline();//根據函數名字就大概知道,這是處理內核cmdline的函數

        //add by xzj to set ro.rk.soc read from /proc/cpuinfo if not set
        set_soc_if_need();

        // Propagate the kernel variables to internal variables
        // used by init as well as the current required properties.
        export_kernel_boot_props();//這裡就是將處理完cmdline的相關的boot屬性輸出,我們上面已經分析過這個函數了
    }

....
}

先看process_kernel_cmdline函數:

這裡做了兩個動作,改cmdline的許可權和設置import_kernel_nv這個回調函數

static void process_kernel_cmdline() {
    // Don't expose the raw commandline to unprivileged processes.
    chmod("/proc/cmdline", 0440);

    // The first pass does the common stuff, and finds if we are in qemu.
    // The second pass is only necessary for qemu to export all kernel params
    // as properties.
    import_kernel_cmdline(false, import_kernel_nv);
    if (qemu[0]) import_kernel_cmdline(true, import_kernel_nv);
}

回調函數import_kernel_nv將傳入的cmdline中的條目解析並且設置property

static void import_kernel_nv(const std::string& key, const std::string& value, bool for_emulator) {
    if (key.empty()) return;
    if (for_emulator) {
        // In the emulator, export any kernel option with the "ro.kernel." prefix.
        property_set(android::base::StringPrintf("ro.kernel.%s", key.c_str()).c_str(), value.c_str());
        return;
    }

    if (key == "qemu") {
        strlcpy(qemu, value.c_str(), sizeof(qemu));
    } else if (android::base::StartsWith(key, "androidboot.")) {
        property_set(android::base::StringPrintf("ro.boot.%s", key.c_str() + 12).c_str(),
                     value.c_str());
    }
}

再看看import_kernel_cmdline做了什麼動作?

這裡從/proc/cmdline讀出數據,然後以空格“ ”分開數據,for迴圈調用傳入的回調函數指針fn,也就是import_kernel_nv函數,再將分開的數據傳參入回調函數。

void import_kernel_cmdline(bool in_qemu,
                           std::function<void(const std::string&, const std::string&, bool)> fn) {
    std::string cmdline;
    android::base::ReadFileToString("/proc/cmdline", &cmdline);

    for (const auto& entry : android::base::Split(android::base::Trim(cmdline), " ")) {
        std::vector<std::string> pieces = android::base::Split(entry, "=");
        if (pieces.size() == 2) {
            fn(pieces[0], pieces[1], in_qemu);
        }
    }
}

這裡小小的總結下:

從上面的步驟跟蹤下來,發現整體流程是將從boot傳給kernelcmdline中的androidboot.serialno賦給ro.boot.serialno,然後再根據ro.boot.*相關的屬性去設置export_kernel_boot_props函數中prop_map這個數組對應的ro. 屬性。

舉個慄子,此處serialno的流程就該為:

boot- > kernel cmdline -> androidboot.serialno -> ro.boot.serialno -> ro.serialno -> 然後再被prop調用

到這裡,只有kernel cmdline之前的流程不知道了,具體boot是怎麼將一堆東西傳給/proc/cmdline的呢?

好的,安排它~

三,u-Boot層

繼續進uboot目錄搜索一下:

xxx@server01:~/workspace/rk3128_tablet$ grep -nrw "androidboot.serialno" u-boot/
匹配到二進位文件 u-boot/u-boot.bin
匹配到二進位文件 u-boot/common/cmd_bootrk.o
匹配到二進位文件 u-boot/common/built-in.o
匹配到二進位文件 u-boot/uboot.img
匹配到二進位文件 u-boot/u-boot
u-boot/include/fastboot.h:81:#define FASTBOOT_SERIALNO_BOOTARG "androidboot.serialno"
xxx@server01:~/workspace/rk3128_tablet$ 

找到一個FASTBOOT_SERIALNO_BOOTARG,繼續搜它,看誰用了

xtw-cl@server01:~/workspace/pnd_rk3128_tablet$ grep -nrw "FASTBOOT_SERIALNO_BOOTARG" u-boot/
u-boot/common/cmd_bootrk.c:583:         if (!strstr(command_line, FASTBOOT_SERIALNO_BOOTARG)) {
u-boot/common/cmd_bootrk.c:585:                                 "%s %s=%s", command_line, FASTBOOT_SERIALNO_BOOTARG, sn);
u-boot/include/fastboot.h:81:#define FASTBOOT_SERIALNO_BOOTARG "androidboot.serialno"
xtw-cl@server01:~/workspace/pnd_rk3128_tablet$

找到了,u-boot/common/cmd_bootrk.c文件

好的,開始分析源碼:

static void rk_commandline_setenv(const char *boot_name, rk_boot_img_hdr *hdr, bool charge)
{
....

	snprintf(command_line, sizeof(command_line),
			 "%s SecureBootCheckOk=%d", command_line, SecureBootCheckOK);

	char *sn = getenv("fbt_sn#");
	if (sn != NULL) {
		/* append serial number if it wasn't in device_info already */
		if (!strstr(command_line, FASTBOOT_SERIALNO_BOOTARG)) {
			snprintf(command_line, sizeof(command_line),
					"%s %s=%s", command_line, FASTBOOT_SERIALNO_BOOTARG, sn);
		}
	}

	command_line[sizeof(command_line) - 1] = 0;

	setenv("bootargs", command_line);
#endif /* CONFIG_CMDLINE_TAG */
}

從源碼可得知,androidboot.serialno的這個sn參數是通過getenv("fbt_sn#")獲取到的,好的,繼續搜索fbt_sn#看看是哪裡設置的這個環境變數

xxx@server01:~/workspace/rk3128_tablet$ grep -nrw "fbt_sn#" u-boot/
匹配到二進位文件 u-boot/u-boot.bin
u-boot/common/cmd_bootrk.c:580: char *sn = getenv("fbt_sn#");
匹配到二進位文件 u-boot/common/cmd_fastboot.o
匹配到二進位文件 u-boot/common/cmd_bootrk.o
u-boot/common/cmd_fastboot.c:662:       //setenv("fbt_sn#", serial_number);
u-boot/common/cmd_fastboot.c:668:       char *sn = getenv("fbt_sn#");
匹配到二進位文件 u-boot/common/built-in.o
u-boot/board/rockchip/rk33xx/rk33xx.c:226:              setenv("fbt_sn#", tmp_buf);
u-boot/board/rockchip/rk32xx/rk32xx.c:220:              setenv("fbt_sn#", tmp_buf);
匹配到二進位文件 u-boot/board/rockchip/rk32xx/rk32xx.o
匹配到二進位文件 u-boot/board/rockchip/rk32xx/built-in.o
匹配到二進位文件 u-boot/uboot.img
匹配到二進位文件 u-boot/u-boot
xxx@server01:~/workspace/rk3128_tablet$ 

可以得知,設setenv的有兩個,但是我們生成的二進位文件是rk32xx.o,所以我們分析rk32xx.c這個源碼。


#ifdef CONFIG_BOARD_LATE_INIT
extern char bootloader_ver[24];
int board_late_init(void)
{
	debug("board_late_init\n");
	
    ....

	char tmp_buf[32];
	/* rk sn size 30bytes, zero buff */
	memset(tmp_buf, 0, 32);
	if (rkidb_get_sn(tmp_buf)) {
		setenv("fbt_sn#", tmp_buf);
	}

	debug("fbt preboot\n");
	board_fbt_preboot();

	return 0;
}
#endif

從上面可以看出設進fbt_sn#屬性名字的tmp_buf是從rkidb_get_sn函數獲取的,so繼續。

順便提一句,board_late_init會在環境初始化函數中調用,而它會被啟動的更底層的彙編程式調用,這裡不展開講

搜一下這個rkidb_get_sn函數

xxxx@server01:~/workspace/rk3128_tablet$ grep -nrw "rkidb_get_sn" u-boot/
u-boot/board/rockchip/rk33xx/rk33xx.c:225:      if (rkidb_get_sn(tmp_buf)) {
u-boot/board/rockchip/rk32xx/rk32xx.c:219:      if (rkidb_get_sn(tmp_buf)) {
匹配到二進位文件 u-boot/board/rockchip/rk32xx/rk32xx.o
匹配到二進位文件 u-boot/board/rockchip/rk32xx/built-in.o
u-boot/board/rockchip/common/rkloader/idblock.c:565:int rkidb_get_sn(char* buf)
u-boot/board/rockchip/common/rkloader/idblock.su:7:idblock.c:565:5:rkidb_get_sn 16      static
u-boot/board/rockchip/common/rkloader/idblock.h:252:int rkidb_get_sn(char *buf);
匹配到二進位文件 u-boot/board/rockchip/common/rkloader/idblock.o
匹配到二進位文件 u-boot/board/rockchip/common/built-in.o
u-boot/u-boot.map:1468: .text.rkidb_get_sn
u-boot/u-boot.map:1470:                0x0000000060008bc4                rkidb_get_sn
u-boot/u-boot.map:4608: .rel.text.rkidb_get_sn
u-boot/System.map:219:60008bc4 T rkidb_get_sn
匹配到二進位文件 u-boot/u-boot
xxxx@server01:~/workspace/rk3128_tablet$

實現在u-boot/board/rockchip/common/rkloader/idblock.c文件,打開它

int  (char* buf)
{
	int size;
	Sector3Info *pSec3;
	uint8 *pidbbuf = (uint8 *)gIdDataBuf;

	pSec3 = (Sector3Info *)(pidbbuf + IDBLOCK_SIZE * IDBLOCK_SN);

	size = pSec3->snSize;
	if (size <= 0 || size > SN_MAX_SIZE) {
		PRINT_E("empty serial no.\n");
		return false;
	}
	strncpy(buf, (char *)pSec3->sn, size);
	buf[size] = '\0';
	PRINT_E("sn: %s\n", buf);
	return true;
}

可以看出是通過ID Block去讀的,通過地址偏移取值拿到的,那我們繼續找尋哪裡給這個gIdDataBuf賦的值。

搜索一下,根據搜索出的信息去篩選

xxxxx@server01:~/workspace/rk3128_tablet$ grep -nrw "gIdDataBuf" u-boot/
匹配到二進位文件 u-boot/board/rockchip/common/storage/storage.o
u-boot/board/rockchip/common/storage/storage.h:197:EXT uint32 gIdDataBuf[512] __attribute__((aligned(ARCH_DMA_MINALIGN)));
u-boot/board/rockchip/common/SecureBoot/SecureBoot.c:133:       FlashSramLoadStore(&gIdDataBuf[384], 1536, 1, 512);  // idblk sn info
匹配到二進位文件 u-boot/board/rockchip/common/SecureBoot/SecureBoot.o
匹配到二進位文件 u-boot/board/rockchip/common/mediaboot/sdmmcBoot.o
u-boot/board/rockchip/common/mediaboot/sdmmcBoot.c:120:         ret1 = SDM_Read(ChipSel, SD_CARD_BOOT_PART_OFFSET, 4, gIdDataBuf);
u-boot/board/rockchip/common/mediaboot/sdmmcBoot.c:123:                 if (gIdDataBuf[0] == 0xFCDC8C3B) {
匹配到二進位文件 u-boot/board/rockchip/common/mediaboot/sdmmcBoot.c
u-boot/board/rockchip/common/mediaboot/UMSBoot.c:307:                   __UMSReadLBA(usb_stor_curr_dev, UMS_BOOT_PART_OFFSET, gIdDataBuf, 4);
u-boot/board/rockchip/common/mediaboot/UMSBoot.c:308:                   if (gIdDataBuf[0] == 0xFCDC8C3B) {
u-boot/board/rockchip/common/mediaboot/UMSBoot.c:309:                           if (0 == gIdDataBuf[128+104/4]) {
u-boot/board/rockchip/common/mediaboot/UMSBoot.c:313:                           } else if (1 == gIdDataBuf[128+104/4]) {
u-boot/board/rockchip/common/mediaboot/sdhciBoot.c:53:  block_mmc_read(SDHCI_EMMC_DEV_ID, SD_CARD_BOOT_PART_OFFSET, 4, gIdDataBuf);
u-boot/board/rockchip/common/rkloader/idblock.c:30:extern uint32 gIdDataBuf[512];
u-boot/board/rockchip/common/rkloader/idblock.c:505:            pdst = (uint8 *)gIdDataBuf;
u-boot/board/rockchip/common/rkloader/idblock.c:512:    GetIdblockDataNoRc4((char *)&gIdDataBuf[128 * 2], 512);
u-boot/board/rockchip/common/rkloader/idblock.c:513:    GetIdblockDataNoRc4((char *)&gIdDataBuf[128 * 3], 512);
u-boot/board/rockchip/common/rkloader/idblock.c:532:    if (gIdDataBuf[0] == 0xFCDC8C3B) {
u-boot/board/rockchip/common/rkloader/idblock.c:533:            memcpy((char *)&idb0_info, gIdDataBuf, 512);
u-boot/board/rockchip/common/rkloader/idblock.c:545:    uint8 *buf = (uint8 *)&gIdDataBuf[0];
u-boot/board/rockchip/common/rkloader/idblock.c:569:    uint8 *pidbbuf = (uint8 *)gIdDataBuf;
u-boot/board/rockchip/common/rkloader/idblock.c:588:    uint8 *pidbbuf = (uint8 *)gIdDataBuf;
u-boot/board/rockchip/common/rkloader/idblock.c:609:    uint8 *pidbbuf = (uint8 *)gIdDataBuf;
匹配到二進位文件 u-boot/board/rockchip/common/rkloader/idblock.o
匹配到二進位文件 u-boot/board/rockchip/common/built-in.o
u-boot/u-boot.map:6203: .bss.gIdDataBuf
u-boot/u-boot.map:6205:                0x000000006009b5c0                gIdDataBuf
u-boot/System.map:1464:6009b5c0 B gIdDataBuf
匹配到二進位文件 u-boot/u-boot
xxxxx@server01:~/workspace/rk3128_tablet$

我們這裡的目的是需要知道哪裡給gIdDataBuf其賦值,所以我們直接查看有編譯到產出.o文件的並且有可能是直接給它賦值的文件及函數位置。

文件位置:u-boot/board/rockchip/common/mediaboot/sdmmcBoot.c

從名字就可以大概看出,這是操作sdmmc的,也就是eMMCSD卡的地方,好的繼續看函數。


uint32 SdmmcInit(uint32 ChipSel)
{
	int32 ret1 = SDM_SUCCESS;
	uint32 ioctlParam[5] = {0, 0, 0, 0, 0};

	.....
	
    ret1 = SdmmcReinit(ChipSel);
	if (ret1 == SDM_SUCCESS) { /* 卡能識別 */
#ifdef EMMC_NOT_USED_BOOT_PART
		ioctlParam[0] = ChipSel;
		
        .....
            
		/* id blk data */
		ret1 = SDM_Read(ChipSel, SD_CARD_BOOT_PART_OFFSET, 4, gIdDataBuf);//這裡就是載入eMMC中id block數據的地方
#ifdef RK_SDCARD_BOOT_EN
		if (ChipSel == 0) {
			if (gIdDataBuf[0] == 0xFCDC8C3B) {
				gSdCardInfoTbl[ChipSel].FwPartOffset = SD_CARD_FW_PART_OFFSET;
				if (0 == gIdDataBuf[128 + 104 / 4]) { /* sd卡升級 */
					gsdboot_mode = SDMMC_SDCARD_UPDATE;
					PRINT_E("SDCard Update.\n");
				} else if (1 == gIdDataBuf[128 + 104 / 4]) { /* sd 卡運行 */
					gsdboot_mode = SDMMC_SDCARD_BOOT;
					PRINT_E("SDCard Boot.\n");
				}
			} else {
	.....
	return ERROR;
}

好的,從上面可以看出,gIdDataBuf里是存在eMMC上某個地方的數據,通過SDM_Read去讀取載入的。

其實到這裡,已經非常明確了,但是秉著一探到底的原則,我們繼續往前~

看看SdmmcInit是哪裡調用的?

經過grep跟蹤大法一頓操作,加上分析,發現SdmmcInit是以方法結構體的方式存在於u-boot/board/rockchip/common/storage/storage.c文件中,具體如下:

#ifdef RK_SDMMC_BOOT_EN
static MEM_FUN_T emmcFunOp =
{
	2,
	BOOT_FROM_EMMC,
	0,
	SdmmcInit,
	SdmmcReadID,
	SdmmcBootReadPBA,
	SdmmcBootWritePBA,
	SdmmcBootReadLBA,
	SdmmcBootWriteLBA,
	SdmmcBootErase,
	SdmmcReadFlashInfo,
	SdmmcCheckIdBlock,
	NULL,
	NULL,
	NULL,
	SdmmcGetCapacity,
	SdmmcSysDataLoad,
	SdmmcSysDataStore,
	SdmmcBootEraseData,
};
#endif

然後又被包含在了一個結構體指針數組裡:

static MEM_FUN_T *memFunTab[] = 
{
#ifdef RK_UMS_BOOT_EN
	&UMSFunOp,
#endif

#ifdef RK_SDCARD_BOOT_EN
	&sd0FunOp,
#endif

#if defined(RK_SDMMC_BOOT_EN) || defined(RK_SDHCI_BOOT_EN)
	&emmcFunOp,
#endif

#ifdef RK_FLASH_BOOT_EN
	&NandFunOp,
#endif

#ifdef CONFIG_RK_NVME_BOOT_EN
	&nvmeFunOp,
#endif
};

最後被StorageInit調用:

#define MAX_MEM_DEV	(sizeof(memFunTab)/sizeof(MEM_FUN_T *))


int32 StorageInit(void)
{
	uint32 memdev;

	memset((uint8*)&g_FlashInfo, 0, sizeof(g_FlashInfo));
	for(memdev=0; memdev<MAX_MEM_DEV; memdev++)
	{
		gpMemFun = memFunTab[memdev];
		if(memFunTab[memdev]->Init(memFunTab[memdev]->id) == 0)
		{
			memFunTab[memdev]->Valid = 1;
			StorageReadFlashInfo((uint8*)&g_FlashInfo);
			vendor_storage_init();
			return 0;
		}
	}

	/* if all media init error, usding null function */
	gpMemFun = &nullFunOp;

	return -1;
}

然後被在RK的板級邏輯u-boot/board/rockchip/rk32xx/rk32xx.c中的board_storage_init調用

int board_storage_init(void)
{
	int ret = 0;

	if (StorageInit() == 0) {
		printf("storage init OK!\n");
		ret = 0;
	} else {
		printf("storage init fail!\n");
		ret = -1;
	}

	return ret;
}

board_storage_init又在u-boot/arch/arm/lib/board.cuboot啟動階段被調用:


/************************************************************************
 *
 * This is the next part if the initialization sequence: we are now
 * running from RAM and have a "normal" C environment, i. e. global
 * data can be written, BSS has been cleared, the stack size in not
 * that critical any more, etc.
 *
 ************************************************************************
 */

void board_init_r(gd_t *id, ulong dest_addr)
{
	ulong malloc_start;
#if !defined(CONFIG_SYS_NO_FLASH)
	ulong flash_size;
#endif

	.....

#ifdef CONFIG_ROCKCHIP
	board_storage_init();//這裡調用的
#endif

	.....
	
#ifdef CONFIG_BOARD_LATE_INIT
	board_late_init();
#endif
	.....
	/* main_loop() can return to retry autoboot, if so just run it again. */
	for (;;) {
		main_loop();
	}

	/* NOTREACHED - no way out of command loop except booting */
}

然後來到uboot最靠前的彙編s文件u-boot/arch/arm/lib/crt0.S里,調用了board_init_r這個C函數:


/* Set up final (full) environment */

	bl	c_runtime_cpu_setup	/* we still call old routine here */

	ldr	r0, =__bss_start	/* this is auto-relocated! */
	ldr	r1, =__bss_end		/* this is auto-relocated! */

	mov	r2, #0x00000000		/* prepare zero to clear BSS */

clbss_l:cmp	r0, r1			/* while not at end of BSS */
	strlo	r2, [r0]		/* clear 32-bit BSS word */
	addlo	r0, r0, #4		/* move to next */
	blo	clbss_l

	bl coloured_LED_init
	bl red_led_on

	/* call board_init_r(gd_t *id, ulong dest_addr) */
	mov     r0, r9                  /* gd_t */
	ldr	r1, [r9, #GD_RELOCADDR]	/* dest_addr */
	/* call board_init_r */
	ldr	pc, =board_init_r	/* this is auto-relocated! */

	/* we should not return here. */

#endif

ENDPROC(_main)

四,總結

uboot在啟動時,從eMMC某塊區域讀取了一定位元組大小的數據,根據晶元廠商定義的偏移地址取出一組sn號,然後再用這串sn號以“androidboot.serialno=”首碼設進cmdline參數里,在啟動kernel時傳入,然後kernel將收到的cmdline數據寫入到/proc/cmdline里,接著啟動系統的第一個程式init程式,init程式從/proc/cmdline讀出對應的“androidboot.serialno“數據以“ro.boot.serialno”名字設置屬性,然後drmserviceinit程式設置的"ro.boot.serialno"屬性來設置“ro.serialno,最後系統通過getprop ro.serialno來獲取,APP通過Build.getSerial()Build.SERIAL來獲取。

至此,大功告成

end

感謝閱讀~

希望能幫到你~

see you~

碼字不易,轉載請註明原作者 ~ (from:https://erdong.work

本文來自博客園,作者:耳東Sir,轉載請註明原文鏈接:https://www.cnblogs.com/erdongsir/p/17152900.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Prometheus安裝部署 一,下載安裝包並解壓 **下載地址:**https://github.com/prometheus/prometheus/releases 因為伺服器上下載速度太慢,所以可以提前在物理機上下載上傳到伺服器,本次安裝使用的版本為:prometheus-2.37.5.lin ...
  • 問題截圖: 問題: 本機新安裝的jenkins,之前已經將/var/lib/jenkins/.ssh/id_rsa.pub copy 到目標A主機,運行正常,今天突然出現此問題 排查: 1./etc/passwd 顯示jenkins用戶最後是/bin/false無登錄許可權 2.鑒於B主機使用同樣的構 ...
  • 準備工作 下載安裝vagrant https://releases.hashicorp.com/vagrant/2.3.4/vagrant_2.3.4_windows_amd64.msi 選擇自己需要的版本傻瓜式安裝 下載安裝vartualbox https://download.virtualbo ...
  • 前言 在K8s的Node節點上經常有其他進程和Pod爭搶記憶體資源,導致該Node出現OOM現象,最終導致運行在該Node節點上Pod被OS給Kill掉; 採用監控系統和日誌系統對該現象進行監控報警,並通過日誌系統收集的日誌進行佐證; 一、Top命令 我們平時會部署一些應用到Linux伺服器,所以經常 ...
  • 前言 Redis中有5大數據類型,分別是字元串String、列表List、集合Set、哈希Hash、有序集合Zset,本篇介紹Redis的字元串String #Redis字元串 String是Redis最基本的類型,你可以理解成與Memcached一模一樣的類型,一個key對應一個value Str ...
  • 2月15日,數倉軟體巨頭Teradata宣佈根據其對中國當前和未來商業環境的慎重評估,將逐步結束在中國的直接運營,後續進入中國公司的關閉程式。 一石激起千層浪,這一消息,在國內的To B市場引起了廣泛關註。Teradata這家進入中國市場26年,為國內數倉發展帶來重要啟蒙,奠定數據服務人才基礎,被業 ...
  • 前言 redis安裝在Linux伺服器上,系統為centos7,安裝的版本為redis6.2.10 下載與安裝 下載地址:https://redis.io/download/#redis-downloads 點擊上面的鏈接就能進入redis下載頁面,最新的目前是7.0 這裡使用的是6.2.10的版本 ...
  • 閱識風雲是華為雲信息大咖,擅長將複雜信息多元化呈現,其出品的一張圖(雲圖說)、深入淺出的博文(雲小課)或短視頻(雲視廳)總有一款能讓您快速上手華為雲。更多精彩內容請單擊此處。 摘要:Hue是一組WEB應用,用於和MRS大數據組件進行交互,能夠幫助用戶瀏覽HDFS,進行Hive查詢,啟動MapRedu ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...