關於ro.serialno
這個屬性,相信大家都不陌生了,應用層的Build.getSerial()
,Build.SERIAL
等均是直接或間接的獲取了這個屬性值。接下來從boot到系統應用,小小的分析一下它的整個流程:
由於是APP經常使用,那我們從應用層分析到底層kernel/boot
好的,我們進入安卓原始碼目錄,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傳給kernel的cmdline中的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的呢?
好的,安排它~
繼續進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
的,也就是eMMC
或SD
卡的地方,好的繼續看函數。
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.c
uboot啟動階段被呼叫:
/************************************************************************
*
* 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」名字設定屬性,然後drmservice用init程式設定的"ro.boot.serialno"屬性來設定「ro.serialno,最後系統通過getprop ro.serialno
來獲取,APP通過Build.getSerial()
,Build.SERIAL
來獲取。
至此,大功告成
感謝閱讀~
希望能幫到你~
see you~
碼字不易,轉載請註明原作者 ~ (from:https://erdong.work)
本文來自部落格園,作者:耳東Sir,轉載請註明原文連結:https://www.cnblogs.com/erdongsir/p/17152900.html