pmon构建过程分析

6 篇文章 1 订阅

以编译ls2k pai 为例,编译步骤如下

$ cd zloader.ls2k
$ make cfg				// 
$ make tgt=rom			// 生成gzrom.bin
$ make dtb				// 生成gzrom-dtb.bin ,即包含设备树

构建过程如下:

  1. 通过脚本getname获取当前的目录名,以确定具体的板卡,设置相应的变量
  2. 处理libc, libm, libzMakefile
  3. 创建文件夹Targets/LS2K/compile
  4. 进入Targets/LS2K/conf 目录,执行pmoncfg ls2kls2k 是配置文件指定选用哪些驱动模块和命令, files.LS2Kconf/files指定了每个模块或命令对应的实现文件和宏定义。执行pmoncfg ls2k 之后会生成Targets/LS2K/compile/ls2k目录,目录里面有相应的文件。
  5. 跳转到 Targets/LS2K/compile/ls2k 执行命令make depend clean
  6. 根据编译时指定的参数tgt=romtgt=romld.script.S生成Targets/LS2K/conf/ld.script文件。 主要区别是start的地址不同。
  7. Target/LS2K/compile/ls2k/生成pmon, pmon.bin, start.o,拷贝Target/LS2K/compile/ls2k/start.o 到当前位置(zloader.ls2k)。其中pmon.bin是通过objcopy -o binary pmon而来, 如果编译时参入DEBUG=-g 参数则会生成pmon.gdb 文件,用于调试。
  8. pmon.bin通过gzip 压缩成pmon.bin.gz
  9. pmon.bin.gz 通过bin2c 转换成为C数组文件pmon.bin.c,数组名为biosdata
  10. 通过 ./genrom ../Targets/LS2K/compile/ls2k/pmon > initmips.c 生成initmips.c
  11. genrom 的功能是通过执行objdump -x pmon获取pmon的符号表和重定位信息,通过匹配正则表达式从中获取 startinitmips的地址,initmips.c的功能用于将biosdata解压到start处,之后再跳转到initmips处执行。 所以pmon的执行过程是从start.S 开始跳转到zloader.ls2k/initmips.c中的initmips,之后再跳转到Targets/LS2K/ls2k/tgt_machdep.c中的initmips函数
  12. zloader.c inflate.c malloc.c memop.c pmon.bin.c initmips.c 编译成zloader.o
  13. 链接start.ozloader.o 生成gzrom
  14. 通过objcopygzrom转成raw binary gzrom.bin即去除符号表和重定位信息。
  15. 进入Targets/LS2K/compile/ls2k,以Targets/LS2K/conf/ls2k.dts为输入文件,LS2K.dtb.i为输出文件,执行make dtb通过dtcLS2K.dtb.i转换成LS2K.dtb
  16. 通过../tools/pmonenv.pydtb和内核启动参数追加到gzrom.bin形成gzrom-dtb.bin

简化后的Makefile.inc

ifndef PMONCC
CC=$(CROSS_COMPILE)gcc -mabi=32
CROSS_COMPILE ?= mipsel-linux-
else
CC=${PMONCC}
endif


ifeq "$(shell echo ${CC}|grep  'mips-elf-')" ""
 ifeq "$(shell echo ${CC}|grep  '\-mabi=64')" ""
LD        = $(CROSS_COMPILE)ld  -m elf32ltsmip -G 0 -static -n -nostdlib
OUT_FORMAT="elf32-tradlittlemips"
 else
LD        = $(CROSS_COMPILE)ld  -m elf64ltsmip -G 0 -static -n -nostdlib
OUT_FORMAT="elf64-tradlittlemips"
 endif
else
ENDIAN_FLAG= -EL
LD        = $(CROSS_COMPILE)ld  -m elf32ebmip -G 0 -static -n -nostdlib -EL
CC :=${CC} -EL
OUT_FORMAT="elf32-littlemips"
endif

RAMSTARTADDR?=0xffffffff88000000
GZROMSTARTADDR?=0xffffffff8f900000
ROMSTARTADDR?=0xffffffff8f010000
export CROSS_COMPILE
export CC += ${MYCC}  -mno-abicalls -fno-pic
export LD
export MKDEP=makedepend
ifeq ("${tgt}","rom")
gencode=./genrom
endif

ifndef tgt
help:
    @echo use make tgt=sim for sim in linux
    @echo use make tgt=rom for generate romfile gzrom.bin
    @echo use make tgt=ram for generate ramfile gzram
    @echo use make cfg     for config
    @echo use make recfg   for change config
    @echo use make tgt=rom DEBUG=-g MYCC="'"-g3 -DMYDBG='"printf(\"debug:%s,%d\\n\",__FILE__,__LINE__);"'"'" to support MYDBG macro. 
else
all: ${tgt}
ejtag_rom ejtag_rom1 ejtag_ram rom: clean ${START} zloader.o 
    gcc  -DSTARTADDR=${GZROMSTARTADDR} -DOUT_FORMAT=\"${OUT_FORMAT}\" -DOUT_ARCH=mips -Umips -E -P ld.script.S  > ld.script
    ${LD} -T ld.script -e start -o gzrom ${START} zloader.o 
    ${CROSS_COMPILE}objcopy -O binary gzrom gzrom.bin

${START}:
    rm -f ../Targets/${TARGET}/compile/${TARGETEL}/${START}
    gcc  -DSTARTADDR=${ROMSTARTADDR} -DOUT_FORMAT=\"${OUT_FORMAT}\" -DOUT_ARCH=mips -Umips -E -P ld.script.S  > ../Targets/${TARGET}/conf/ld.script
    make -C ../Targets/${TARGET}/compile/${TARGETEL}/
    cp ../Targets/${TARGET}/compile/${TARGETEL}/${START} .

zloader.o: zloader.c inflate.c  malloc.c  memop.c  pmon.bin.c initmips.c
    $(CC) -c zloader.c ${ZLOADER_OPTIONS} -DMEMSIZE=${MEMSIZE}

initmips.c:  ../Targets/${TARGET}/compile/${TARGETEL}/pmon
    ${gencode} $< > initmips.c

pmon.bin.c:  ../Targets/${TARGET}/compile/${TARGETEL}/pmon.bin
    gzip $< -c > pmon.bin.gz
    ./bin2c pmon.bin.gz pmon.bin.c biosdata

endif

dtb:
    make -C ../Targets/${TARGET}/compile/${TARGETEL}/ DTB_O=`pwd`/${TARGET}.dtb.i DTB_I=`pwd`/../Targets/${TARGET}/conf/${TARGET}.dts dtb
    ./dtc -I dts -O dtb -o ${TARGET}.dtb  ${TARGET}.dtb.i
    ( echo  "#include <include/load_dtb.h>";echo NVRAM_OFFS; echo DTB_OFFS; )| make -C ../Targets/${TARGET}/compile/${TARGETEL}/ DTB_O=`pwd`/dtbinfo.txt DTB_I=- dtb
    [ -f gzrom.bin ] && cp gzrom.bin gzrom-dtb.bin && python ../tools/pmonenv.py -O $$((`tail -n 1 dtbinfo.txt`)) -o $$((`tail -n 2 dtbinfo.txt|head -n 1`)) -f gzrom-dtb.bin -d ${TARGET}.dtb -w  al=\(usb0,0\)/boot/vmlinuz al1=\(wd0,0\)/boot/vmlinuz append="'console=ttyS0,115200 console=tty initcall_debug=1 loglevel=20'" FR=1
cleanall: clean
    make -C ../Targets/${TARGET}/compile/${TARGETEL}/ clean
clean:
    rm -rf *.o zlib_gzip zloader pmon.bin.c gzrom gzrom.bin gzram  initmips.c pmon.bin.gz zlib_deflate/*.o zlib_inflate/*.o zlib_gzrom zlib_gzrom.bin
cfg:
    # DO NOT DELETE
    perl -i -ne 'print;exit if(/^# DO NOT DELETE/);' ../lib/libc/Makefile 
    perl -i -ne 'print;exit if(/^# DO NOT DELETE/);' ../lib/libm/Makefile 
    perl -i -ne 'print;exit if(/^# DO NOT DELETE/);' ../lib/libz/Makefile 
    mkdir -p ../Targets/${TARGET}/compile
    cd ../Targets/${TARGET}/conf/;pmoncfg ${TARGETEL}
    make -C ../Targets/${TARGET}/compile/${TARGETEL}/ depend clean

详细分析

make 命令是通过对Makefile 文件进行解释编译的,故我们来看Makefile 文件。

$ cat Makefile
include $(shell ./getname)
# DO NOT DELETE

通过上面可知是要执行的Makefile文件名为执行getname后得到的结果

$ cat getname 
#echo $(pwd0=$(pwd);pwd1=$(dirname $pwd0)/zloader.;echo Makefile.${pwd0#$pwd1})
pwd=$(pwd);
if [ "${pwd#*/zloader.}" = "$pwd" ];then
echo Makefile.cpci
else
echo Makefile.${pwd#*/zloader.}
fi

通过上面可知是得到的是Makefile.ls2k

$ cat Makefile.ls2k
TARGET=LS2K
TARGETEL=ls2k
export START=start.o
MEMSIZE=128
ZLOADER_OPTIONS=-mips3
include Makefile.inc

通过查看Makefile.ls2k可知其他主要是定义了一些变量和调用Makefile.inc

$ cat Makefile.inc 
ifndef PMONCC
CC=$(CROSS_COMPILE)gcc -mabi=32
CROSS_COMPILE ?= mipsel-linux-
else
CC=${PMONCC}
endif


ifeq "$(shell echo ${CC}|grep  'mips-elf-')" ""
 ifeq "$(shell echo ${CC}|grep  '\-mabi=64')" ""
LD		= $(CROSS_COMPILE)ld  -m elf32ltsmip -G 0 -static -n -nostdlib
OUT_FORMAT="elf32-tradlittlemips"
 else
LD		= $(CROSS_COMPILE)ld  -m elf64ltsmip -G 0 -static -n -nostdlib
OUT_FORMAT="elf64-tradlittlemips"
 endif
else
ENDIAN_FLAG= -EL
LD		= $(CROSS_COMPILE)ld  -m elf32ebmip -G 0 -static -n -nostdlib -EL
CC :=${CC} -EL
OUT_FORMAT="elf32-littlemips"
endif

RAMSTARTADDR?=0xffffffff88000000
GZROMSTARTADDR?=0xffffffff8f900000
ROMSTARTADDR?=0xffffffff8f010000
export CROSS_COMPILE
export CC += ${MYCC}  -mno-abicalls -fno-pic
export LD
export MKDEP=makedepend
ifeq ("${tgt}","rom")
gencode=./genrom
endif

ifeq ("${tgt}","ejtag_rom")
gencode=./genrom_ejtag
endif

ifeq ("${tgt}","ejtag_rom1")
gencode=./genrom
CC += -DBOOT_FROM_EJTAG
endif

ifeq ("${tgt}","ejtag_ram")
gencode=./genrom
ROMSTARTADDR=0xffffffffff200200
endif

ifeq ("${tgt}","rom1")
gencode=./genrom
endif


ifdef obj
export CC += -g3
pwd=$(shell pwd)
gcc_E:
	cd ../Targets/${TARGET}/compile/${TARGETEL};${pwd}/mymake  ${obj} > /tmp/tmp.c
endif

ifndef tgt
help:
	@echo use make tgt=sim for sim in linux
	@echo use make tgt=rom for generate romfile gzrom.bin
	@echo use make tgt=ram for generate ramfile gzram
	@echo use make cfg     for config
	@echo use make recfg   for change config
	@echo use make tgt=rom DEBUG=-g MYCC="'"-g3 -DMYDBG='"printf(\"debug:%s,%d\\n\",__FILE__,__LINE__);"'"'" to support MYDBG macro. 
else
all: ${tgt}
sim: clean pmon.bin.c
	gcc -o zloader  -DSIM zloader.c
ejtag_rom ejtag_rom1 ejtag_ram rom: clean ${START} zloader.o 
	gcc  -DSTARTADDR=${GZROMSTARTADDR} -DOUT_FORMAT=\"${OUT_FORMAT}\" -DOUT_ARCH=mips -Umips -E -P ld.script.S  > ld.script
	${LD} -T ld.script -e start -o gzrom ${START} zloader.o 
	${CROSS_COMPILE}objcopy -O binary gzrom gzrom.bin

ram: clean startram.o zloader.o 
	${CC}  -DSTARTADDR=${RAMSTARTADDR} -DOUT_FORMAT=\"${OUT_FORMAT}\" -DOUT_ARCH=mips -Umips -E -P ld.script.S  > ld.script
	${LD}  -T ld.script -e start -o gzram startram.o zloader.o 

${START}:
	rm -f ../Targets/${TARGET}/compile/${TARGETEL}/${START}
	gcc  -DSTARTADDR=${ROMSTARTADDR} -DOUT_FORMAT=\"${OUT_FORMAT}\" -DOUT_ARCH=mips -Umips -E -P ld.script.S  > ../Targets/${TARGET}/conf/ld.script
	make -C ../Targets/${TARGET}/compile/${TARGETEL}/
	cp ../Targets/${TARGET}/compile/${TARGETEL}/${START} .

startram.o:
	gcc  -DSTARTADDR=${RAMSTARTADDR} -DOUT_FORMAT=\"${OUT_FORMAT}\" -DOUT_ARCH=mips -Umips -E -P ld.script.S  > ../Targets/${TARGET}/conf/ld.script
	make -C ../Targets/${TARGET}/compile/${TARGETEL}/ DEBUG='-g -DMYDBG="printf(\"debug:%s,%d\\n\",__FILE__,__LINE__);"'
	$(CC) -D_LOCORE -G 0  -nostdinc   -DMIPS  -DCONS_BAUD="B115200"  -DCONFIG_CACHE_64K_4WAY  -D_KERNEL -D__OpenBSD__ -DPMON -D__PMON__ -mno-abicalls -c startram.S ${ZLOADER_OPTIONS} -D MEMSIZE=${MEMSIZE}
# $(CC) -D_LOCORE -G 0  -nostdinc   -DMIPS  -DCONS_BAUD="B115200"  -DCONFIG_CACHE_64K_4WAY  -D_KERNEL -D__OpenBSD__ -DPMON -D__PMON__ -EL -mno-abicalls -mcpu=r4000 -c startram.S -D MEMSIZE=${MEMSIZE}
zloader.o: zloader.c inflate.c  malloc.c  memop.c  pmon.bin.c initmips.c
	$(CC) -c zloader.c ${ZLOADER_OPTIONS} -DMEMSIZE=${MEMSIZE}

rom1: clean ${START} zloader1.o 
	gcc  -DSTARTADDR=0xffffffff9fc00000 -D_ROM1 -DOUT_FORMAT=\"${OUT_FORMAT}\" -DOUT_ARCH=mips -Umips -E -P ld.script.S  > ld.script
	${LD} -T ld.script -e start -o gzrom ${START} zloader.o 
	${CROSS_COMPILE}objcopy -O binary gzrom gzrom.bin

zloader1.o: zloader.c inflate.c  malloc.c  memop.c  pmon.bin.c initmips.c program.o xmodem.o
	sed -i 's/initmips/initmips1/' initmips.c
	sed -i 's/flush_cache2();//;s/flush_cache();//' initmips.c
	$(CC) -g -c -o zloader.tmp.o zloader.c ${ZLOADER_OPTIONS} -DMEMSIZE=${MEMSIZE}
	${LD}  -r -o zloader.o zloader.tmp.o program.o xmodem.o

program.o: program.S
	make -C  ../Targets/${TARGET}/compile/${TARGETEL}/ `pwd`/$@

xmodem.o: xmodem.c
	make -C  ../Targets/${TARGET}/compile/${TARGETEL}/ `pwd`/$@

zlib_loader.o: zlib_loader.c  ./zlib_gzip zlib_pmon.bin.c initmips.c
	cd zlib_inflate;$(CC) -I ../../include -c *.c
	#cd zlib_inflate;$(CC) -I ../../include -EL -mcpu=r4000 -c *.c
	#$(CC) -I ../include -EL -mcpu=r4000 -c zlib_loader.c 
	$(CC) -I ../include -c zlib_loader.c 

initmips.c:  ../Targets/${TARGET}/compile/${TARGETEL}/pmon
	${gencode} $< > initmips.c

zlib_pmon.bin.c:  ../Targets/${TARGET}/compile/${TARGETEL}/pmon.bin
	./zlib_gzip $<  zlib_pmon.bin.gz
	./bin2c zlib_pmon.bin.gz zlib_pmon.bin.c biosdata
	
pmon.bin.c:  ../Targets/${TARGET}/compile/${TARGETEL}/pmon.bin
	gzip $< -c > pmon.bin.gz
	./bin2c pmon.bin.gz pmon.bin.c biosdata

endif

ifeq ("${tgt}","rom")
gencode=./genrom
endif

ifeq ("${tgt}","zlib_rom")
gencode=./genrom
endif


ifndef gencode
gencode=./genram
endif

dtb:
	make -C ../Targets/${TARGET}/compile/${TARGETEL}/ DTB_O=`pwd`/${TARGET}.dtb.i DTB_I=`pwd`/../Targets/${TARGET}/conf/${TARGET}.dts dtb
	./dtc -I dts -O dtb -o ${TARGET}.dtb  ${TARGET}.dtb.i
	( echo  "#include <include/load_dtb.h>";echo NVRAM_OFFS; echo DTB_OFFS; )| make -C ../Targets/${TARGET}/compile/${TARGETEL}/ DTB_O=`pwd`/dtbinfo.txt DTB_I=- dtb
	[ -f gzrom.bin ] && cp gzrom.bin gzrom-dtb.bin && python ../tools/pmonenv.py -O $$((`tail -n 1 dtbinfo.txt`)) -o $$((`tail -n 2 dtbinfo.txt|head -n 1`)) -f gzrom-dtb.bin -d ${TARGET}.dtb -w  al=\(usb0,0\)/boot/vmlinuz al1=\(wd0,0\)/boot/vmlinuz append="'console=ttyS0,115200 console=tty initcall_debug=1 loglevel=20'" FR=1
cleanall: clean
	make -C ../Targets/${TARGET}/compile/${TARGETEL}/ clean
clean:
	rm -rf *.o zlib_gzip zloader pmon.bin.c gzrom gzrom.bin gzram  initmips.c pmon.bin.gz zlib_deflate/*.o zlib_inflate/*.o zlib_gzrom zlib_gzrom.bin
cfg:
	# DO NOT DELETE
	perl -i -ne 'print;exit if(/^# DO NOT DELETE/);' ../lib/libc/Makefile 
	perl -i -ne 'print;exit if(/^# DO NOT DELETE/);' ../lib/libm/Makefile 
	perl -i -ne 'print;exit if(/^# DO NOT DELETE/);' ../lib/libz/Makefile 
	mkdir -p ../Targets/${TARGET}/compile
	cd ../Targets/${TARGET}/conf/;pmoncfg ${TARGETEL}
	make -C ../Targets/${TARGET}/compile/${TARGETEL}/ depend clean
recfg:
	vi ../Targets/${TARGET}/conf/$(TARGETEL)
	make cfg

zlib_gzip: zlib_gzip.c
	cd zlib_deflate/;gcc -I ../../include -c *.c
	cd zlib_inflate/;gcc -I ../../include -c *.c
	gcc -o zlib_gzip zlib_gzip.c zlib_deflate/*.o zlib_inflate/*.o

从上可知Makefile.inc 是真正执行编译的控制文件。我们从编译步骤一步一步来分析。

make cfg,对应的过程

cfg:
	# DO NOT DELETE
	perl -i -ne 'print;exit if(/^# DO NOT DELETE/);' ../lib/libc/Makefile 
	perl -i -ne 'print;exit if(/^# DO NOT DELETE/);' ../lib/libm/Makefile 
	perl -i -ne 'print;exit if(/^# DO NOT DELETE/);' ../lib/libz/Makefile 
	mkdir -p ../Targets/${TARGET}/compile
	cd ../Targets/${TARGET}/conf/;pmoncfg ${TARGETEL}
	make -C ../Targets/${TARGET}/compile/${TARGETEL}/ depend clean

其中TARGETTARGETELMakefile.ls2k 中定义了,分别是LS2Kls2k
make tgt=cfg 对四件事:

  1. 处理libc, libm, libzMakefile
  2. 创建文件夹Targets/LS2K/compile
  3. 进入Targets/LS2K/conf 目录,进行pmoncfg ls2kls2k 是配置文件指定选用哪些驱动模块和命令, files.LS2Kconf/files指定了每个模块或命令对应的实现文件和宏定义。执行pmoncfg ls2k 之后会生成Targets/LS2K/compile/ls2k目录,目录里面有相应的文件。
$ ls
ahci_cdrom.h  cd.h          cmd_shell.h  flash.h    logfile.h  mod_debugger.h     mod_sisfb.h    mod_usb_kbd.h      mod_vesa.h          options     raw_ether.h  usbnet.h
ahci_sd.h     cmd_env.h     cs5536.h     gzip.h     loopdev.h  mod_display.h      mod_smi502.h   mod_usb_ohci.h     mod_vgacon.h        pcibr.h     sdcard.h     wd.h
atp.h         cmd_hist.h    elf32only.h  ide_cd.h   machine    mod_framebuffer.h  mod_smi712.h   mod_usb_storage.h  mod_x86emu.h        pci.h       sd.h
bpfilter.h    cmd_lwdhcp.h  ether.h      ioconf.c   mainbus.h  mod_load.h         mod_symbols.h  mod_usb_uhci.h     mod_x86emu_int10.h  pcinvme.h   target
bridge.h      cmd_more.h    fd.h         iso9660.h  Makefile   mod_s3load.h       mod_tod.h      mod_usb_xhci.h     nand.h              ramfiles.h  tcp.h
  1. 跳转到 Targets/LS2K/compile/ls2k 执行命令make depend clean

Targets/LS2K/compile/ls2k下的Makefile节选

IDENT=-DVGAROM_IN_BIOS -DLOONGSON_2K -DBOOT_PARAM -DVRAM_SIZE="0x10" -DSYSTYPE="\"FCR\"" -DTARGETNAME="\"FCR\"" -DLS2K_STR -DBONITOEL -DDEVBD2E -DMIPS -DINET -DLS3_HT -Dloongson3A3 -D
LSMC_2 -DDDR3_DIMM -DAUTO_DDR_CONFIG -DLOONGSON_GMAC -DDTB -DINTERFACE_3A780E -DCONS_BAUD="B115200" -DMY40IO -DMY61IO -DVGA_BASE="0xb0000000" -DVGA_NO_ROM -DNOPCINAMES -DCONFIG_VIDEO_
16BPP -DNOSNOOP -DHAVE_TOD -DINTERNAL_RTC -DHAVE_NVENV -DHAVE_LOGO -DUSE_SUPERIO_UART -DAUTOLOAD -DCONFIG_CACHE_64K_4WAY -DNVRAM_IN_FLASH -DSET_DDR_FREQ -DIDE_DMA -DIDECD -DFOR_GXEMUL
 -DFLOATINGPT -DCOM3_BASE_ADDR="0xbfe001e0" -DINPUT_FROM_BOTH -DOUTPUT_TO_BOTH -DKBD_CHECK_FAST -DTEST_USB_HOST -DCONFIG_SLOW_PCI_FOR_BROKENDEV -DCONFIG_VIDEO_SW_CURSOR -DHPET_RTC

S!=	echo `/bin/pwd`/../../../..
ENDIAN=EL
ifndef S
S:=$(shell cd ../../../..; pwd)
endif
TARGET= ${S}/Targets/LS2K
SUBTARGET?=ls2k
CPPFLAGS=-mips3

OBJS=	wdc.o mainbus.o pcibr.o if.o if_ethersubr.o if_loop.o if_media.o \
	radix.o raw_cb.o raw_usrreq.o route.o rtsock.o if_ether.o in.o \
...
...
...

CFILES=	$S/sys/dev/ic/wdc.c $S/pmon/dev/mainbus.c $S/pmon/dev/pcibr.c \
	$S/sys/net/if.c $S/sys/net/if_ethersubr.c $S/sys/net/if_loop.c \
...
...
...

SFILES=

all: pmon

pmon: ${SYSTEM_DEP} newvers
	${SYSTEM_LD_HEAD}
	${SYSTEM_LD}
	${SYSTEM_LD_TAIL}


export CFILES OBJS SFILES
include ${S}/Makefile.inc

根目录下的Makefile.inc,这里面主要是包含了libc,libz, libm等的编译规则

$ cat Makefile.inc 
#	$Id: Makefile.Bonito2fdev,v 1.1.1.1 2006/09/14 01:59:09 root Exp $
#
# Makefile for PMON2000 EV64240
#
# This makefile is constructed from a machine description:
#	config machineid
# Most changes should be made in the machine description
#	/sys/arch/pmonppc/conf/``machineid''
# after which you should do
#	config machineid
# Machine generic makefile changes should be made in
#	/sys/arch/pmonppc/conf/Makefile.pmonppc
# after which config should be rerun for all machines of that type.
#
# N.B.: NO DEPENDENCIES ON FOLLOWING FLAGS ARE VISIBLE TO MAKEFILE
#	IF YOU CHANGE THE DEFINITION OF ANY OF THESE RECOMPILE EVERYTHING
#
.SUFFIXES:	.S .c .o

CROSS_COMPILE 	?=mipsel-linux-

#
# Include the make variables (CC, etc...)
#

AS		= $(CROSS_COMPILE)as
LD		= $(CROSS_COMPILE)ld  -m elf32ltsmip -G 0 -static -n -nostdlib
CC		?= $(CROSS_COMPILE)gcc
CPP		= $(CC) -E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nm
STRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdump
RANLIB		= $(CROSS_COMPILE)ranlib
SIZE		= $(CROSS_COMPILE)size

ifeq "$(shell echo ${CC}|grep  'mips-elf-')" ""
 ifeq "$(shell echo ${CC}|grep  '\-mabi=64')" ""
LD		= $(CROSS_COMPILE)ld  -m elf32ltsmip -G 0 -static -n -nostdlib
 else
LD		= $(CROSS_COMPILE)ld  -m elf64ltsmip -G 0 -static -n -nostdlib
 endif
else
ENDIAN_FLAG= -EL
LD		= $(CROSS_COMPILE)ld  -m elf32ebmip -G 0 -static -n -nostdlib -EL
endif

OPT?=	-O2
IDENT:=${IDENT} $(shell echo  ${IDENT}|sed -n 's/.*-DX\([0-9]\+\)x\([0-9]\+\).*/ -DFB_XSIZE=\1 -DFB_YSIZE=\2 /p')
IDENT:=${IDENT} $(shell echo  ${IDENT}|sed -n 's/.*-DCONFIG_VIDEO_\([0-9]\+\)BPP.*/ -DFB_COLOR_BITS=\1 /p')

all: pmon

# source tree is located via $S relative to the compilation directory
ifndef S
S:=$(shell cd ../../../..; pwd)
endif

# Defines

START?=start.o
MACHINE=mips
MACHINE_ARCH=mips
COMPILEDIR=${shell pwd}
OBJDIR=${COMPILEDIR}
PMONDIR=${S}


INCLUDES=	-I. -I${S}/include -I./machine -I${S} \
		-I${S}/sys/arch/${MACHINE}/include -I${S}/sys \
		-I${TARGET} -I${COMPILEDIR} -I${PATH1} -nostdinc -fno-strict-aliasing -fno-pic

ifneq "$(findstring $S/x86emu/src,$(CFILES))" ""
		INCLUDES += -I${S}/x86emu/src/x86emu/ -I${S}/x86emu/src/x86emu/include
else
		INCLUDES += -I${S}/x86emu/int10/x86emu/include -I${S}/x86emu/int10/x86emu/src/x86emu/x86emu 
endif

CPPFLAGS := ${CPPFLAGS} ${ENDIAN_FLAG}	${INCLUDES} ${IDENT} -D_KERNEL -D__OpenBSD__ -DPMON -D__PMON__\
		  -mmemcpy -mno-abicalls -fno-builtin#-march=r4600 
#CWARNFLAGS=	-Werror -Wall -Wmissing-prototypes -Wstrict-prototypes \
		-Wno-uninitialized -Wno-format -Wno-main
CWARNFLAGS=	-Wall -Wstrict-prototypes \
		-Wno-uninitialized -Wno-format -Wno-main
CFLAGS=		${DEBUG} ${CWARNFLAGS} ${OPT} -G 0
AFLAGS=		-D_LOCORE -G 0
LFLAGS=	${ENDIAN_FLAG} -N -G 0 -T../../conf/ld.script -e start
STRIPFLAGS=	-g -S --strip-debug

HOSTCC?=	${CC}
HOSTED_CPPFLAGS=${CPPFLAGS:S/^-nostdinc$//}
HOSTED_CFLAGS=	${CFLAGS}

include ${S}/lib/libc/Makefile.inc
LIBC=${CLIB}
include ${S}/lib/libm/Makefile.inc
LIBM=${MLIB}
include ${S}/lib/libz/Makefile.inc
LIBZ=${ZLIB}

# compile rules: rules are named ${TYPE}_${SUFFIX}${CONFIG_DEP}
# where TYPE is NORMAL, DRIVER, or PROFILE}; SUFFIX is the file suffix,
# capitalized (e.g. C for a .c file), and CONFIG_DEP is _C if the file
# is marked as config-dependent.

USRLAND_C=	${CC} ${CFLAGS} ${CPPFLAGS} ${PROF} -c $<
USRLAND_C_C=	${CC} ${CFLAGS} ${CPPFLAGS} ${PROF} ${PARAM} -c $<

NORMAL_C=	${CC} ${CFLAGS} ${CPPFLAGS} ${PROF} -c $<
NORMAL_C_C=	${CC} ${CFLAGS} ${CPPFLAGS} ${PROF} ${PARAM} -c $<

DRIVER_C=	${CC} ${CFLAGS} ${CPPFLAGS} ${PROF} -c $<
DRIVER_C_C=	${CC} ${CFLAGS} ${CPPFLAGS} ${PROF} ${PARAM} -c $<

NORMAL_S=	${CC} ${AFLAGS} ${CPPFLAGS} -c $<
NORMAL_S_C=	${AS}  ${COPTS} ${PARAM} $< -o $@




# load lines for config "xxx" will be emitted as:
# xxx: ${SYSTEM_DEP}
#	${SYSTEM_LD_HEAD}
#	${SYSTEM_LD}
#	${SYSTEM_LD_TAIL}
#SYSTEM_OBJ=	${START} crtbegin.o param.o ioconf.o ri.o ${OBJS} ${LIBC} ${LIBM} ${LIBZ} \

SYSTEM_OBJ=	${START} crtbegin.o param.o ioconf.o ri.o ${OBJS} ${LIBC} ${LIBM} ${LIBZ} \
		crtend.o
SYSTEM_DEP=	Makefile ${SYSTEM_OBJ}
SYSTEM_LD_HEAD=	rm -f $@
SYSTEM_LD=	@echo ${LD} ${LFLAGS} -o $@ ${LIBDIR} '${SYSTEM_OBJ}' vers.o; \
		${LD} ${LFLAGS} -o $@ ${LIBDIR} ${SYSTEM_OBJ} vers.o -L../../../../examples/math/ -lgcc
SYSTEM_LD_TAIL=	@${SIZE} $@; chmod 755 $@ ; \
		${OBJCOPY} -O binary $@ $@.bin

DEBUG?=
ifneq ("${DEBUG}", "")
LFLAGS+=	-X
SYSTEM_LD_TAIL+=; \
		echo cp $@ $@.gdb; rm -f $@.gdb; cp $@ $@.gdb; \
		echo ${STRIP} ${STRIPFLAGS} $@; ${STRIP} ${STRIPFLAGS} $@
else
LFLAGS+=	-S
endif


param.c: $S/sys/kern/param.c
	rm -f param.c
	cp $S/sys/kern/param.c .

param.o: param.c Makefile
	${NORMAL_C_C}

ioconf.o: ioconf.c
	${NORMAL_C}
ri.o:ri.c Makefile
	${NORMAL_C_C}
ri.c: $S/pmon/arch/mips/ri.c
	rm -f ri.c
	cp $S/pmon/arch/mips/ri.c .
crtbegin.c: $S/pmon/arch/mips/crtbegin.c
	rm -f crtbegin.c
	cp $S/pmon/arch/mips/crtbegin.c .

crtbegin.o: crtbegin.c Makefile
	${NORMAL_C_C}

crtend.c: $S/pmon/arch/mips/crtend.c
	rm -f crtend.c
	cp $S/pmon/arch/mips/crtend.c .

crtend.o: crtend.c Makefile
	${NORMAL_C_C}

newvers: ${SYSTEM_DEP} ${SYSTEM_SWAP_DEP}
	sh $S/conf/newvers.sh
	${CC} ${CFLAGS} ${CPPFLAGS} ${PROF} -c vers.c

clean::
	rm -f eddep *netbsd netbsd.gdb tags *.[io] [a-z]*.s \
		[Ee]rrs linterrs makelinks genassym genassym.o 

lint:
	@lint -hbxncez -DGENERIC -Dvolatile= ${CPPFLAGS} -UKGDB \
		${CFILES} \
		ioconf.c param.c | \
		grep -v 'static function .* unused'

tags:
	@echo "see $S/kern/Makefile for tags"

links:
	egrep '#if' ${CFILES} | sed -f $S/conf/defines | \
	  sed -e 's/:.*//' -e 's/\.c/.o/' | sort -u > dontlink
	echo ${CFILES} | tr -s ' ' '\12' | sed 's/\.c/.o/' | \
	  sort -u | comm -23 - dontlink | \
	  sed 's,../.*/\(.*.o\),rm -f \1; ln -s ../GENERIC/\1 \1,' > makelinks
	sh makelinks && rm -f dontlink

SRCS=	${TARGET}/${SUBTARGET}/start.S \
	param.c ioconf.c ri.c ${CFILES} ${SFILES}
depend:: .depend
.depend: ${SRCS} param.c
	${MKDEP} ${AFLAGS} ${shell echo ${CPPFLAGS}|sed -e 's/ -f[^ ]*//g' } ${TARGET}/${SUBTARGET}/start.S
	${MKDEP} -a ${CFLAGS} ${shell echo ${CPPFLAGS}|sed -e 's/ -f[^ ]*//g' } param.c ioconf.c ${CFILES}
ifneq (${SFILES}, "")
	${MKDEP} -a ${AFLAGS} ${shell echo ${CPPFLAGS}|sed -e 's/ -f[^ ]*//g' } ${SFILES}
endif

# depend on root or device configuration
autoconf.o conf.o: Makefile

# depend on network or filesystem configuration
uipc_proto.o vfs_conf.o: Makefile

${START}: ${TARGET}/${SUBTARGET}/${START:.o=.S} Makefile
	${NORMAL_S}

dtb:
	${CC} ${AFLAGS} ${CPPFLAGS}  -E  -D__ASSEMBLY__ -D__DTS__ -x assembler-with-cpp -o $(DTB_O) $(DTB_I)

zpmon: startz.o
	rm start.o && cp -f startz.o start.o
	make pmon
	make -C ../zboot zpmon
startz.o: ${TARGET}/${SUBTARGET}/startz.S Makefile
	${NORMAL_S}

make tgt=rom 对应的过程

Makefile.inc部分节选

${START}:
    rm -f ../Targets/${TARGET}/compile/${TARGETEL}/${START}
    gcc  -DSTARTADDR=${ROMSTARTADDR} -DOUT_FORMAT=\"${OUT_FORMAT}\" -DOUT_ARCH=mips -Umips -E -P ld.script.S  > ../Targets/${TARGET}/conf/ld.script
    make -C ../Targets/${TARGET}/compile/${TARGETEL}/
    cp ../Targets/${TARGET}/compile/${TARGETEL}/${START} .

根据编译时指定的参数tgt=romtgt=romld.script.S生成Targets/LS2K/conf/ld.script文件。 主要区别是start的地址不同。
编译生成pmon, pmon.bin, start.o,拷贝Target/LS2K/compile/ls2k/start.o 到当前位置(zloader.ls2k
其中pmon.bin是通过objcopy -o binary pmon而来。根目录下Makefile.inc的编译规则:

SYSTEM_LD_TAIL=	@${SIZE} $@; chmod 755 $@ ; \
		${OBJCOPY} -O binary $@ $@.bin

如果编译时参入DEBUG=-g 参数则会生成pmon.gdb 文件,用于调试。根目录下Makefile.inc的编译规则:

DEBUG?=
ifneq ("${DEBUG}", "")
LFLAGS+=	-X
SYSTEM_LD_TAIL+=; \
		echo cp $@ $@.gdb; rm -f $@.gdb; cp $@ $@.gdb; \
		echo ${STRIP} ${STRIPFLAGS} $@; ${STRIP} ${STRIPFLAGS} $@
else
LFLAGS+=	-S
endif

Makefile.inc的部分节选,rom, zloader.o 目标

ejtag_rom ejtag_rom1 ejtag_ram rom: clean ${START} zloader.o 
    gcc  -DSTARTADDR=${GZROMSTARTADDR} -DOUT_FORMAT=\"${OUT_FORMAT}\" -DOUT_ARCH=mips -Umips -E -P ld.script.S  > ld.script
    ${LD} -T ld.script -e start -o gzrom ${START} zloader.o 
    ${CROSS_COMPILE}objcopy -O binary gzrom gzrom.bin

zloader.o: zloader.c inflate.c  malloc.c  memop.c  pmon.bin.c initmips.c
    $(CC) -c zloader.c ${ZLOADER_OPTIONS} -DMEMSIZE=${MEMSIZE}

链接start.ozloader.o 生成gzrom, 通过objcopygzrom转成raw binary gzrom.bin即去除符号表和重定位信息。

生成initmips.c

initmips.c:  ../Targets/${TARGET}/compile/${TARGETEL}/pmon
    ${gencode} $< > initmips.c

pmon.bin.c:  ../Targets/${TARGET}/compile/${TARGETEL}/pmon.bin
    gzip $< -c > pmon.bin.gz
    ./bin2c pmon.bin.gz pmon.bin.c biosdata

ifeq ("${tgt}","rom")
gencode=./genrom
endif

pmon.bin通过gzip 压缩成pmon.bin.gz
pmon.bin.gz 通过bin2c 转换成为C数组文件pmon.bin.c,数组名为biosdata
genrom 的功能是通过执行objdump -x pmon获取pmon的符号表和重定位信息,通过匹配正则表达式从中获取 startinitmips的地址,initmips.c的功能用于将biosdata解压到start处,之后再跳转到initmips处执行。 所以pmon的执行过程是从start.S 开始跳转到zloader.ls2k/initmips.c中的initmips,之后再跳转到Targets/LS2K/ls2k/tgt_machdep.c中的initmips函数

通过上面可知 initmips.c 是通过 ./genrom ../Targets/LS2K/compile/ls2k/pmon > initmips.c 生成的

genrom 的内容:

$ cat genrom
#!/usr/bin/perl 
my ($myedata,$myend,$initmips,$mystart);
open(F,qq(objdump -x $ARGV[0]|));
while(<F>)
{
chomp;
if(/([0-9a-f]+).+_edata/){
   $myedata=qq(0x$1);
 }

if(/([0-9a-f]+).+_end$/){
   $myend=qq(0x$1);
 }
if(/([0-9a-f]+).+initmips$/){
   $myinitmips=qq(0x$1);
 }
if(/([0-9a-f]+).+\s_start$/){
   $mystart=qq(0x$1);
 }
}
printf(<< "END"
void stringserial(char *msg);
void realinitmips(unsigned long long msize);
void enable_cache()
{
	    __asm__ volatile(
		".set mips2;\\n" \\
"        mfc0   \$4,\$16;\\n" \\
"        and    \$4,\$4,0xfffffff8;\\n" \\
"        or     \$4,\$4,0x3;\\n" \\
"        mtc0   \$4,\$16;\\n" \\
"		.set mips0;\\n"
		::
		:"\$4"
		);
}

#ifndef NOCACHE2
void flush_cache2()
{
asm volatile(\
".set mips3;\\n" \\
"	mfc0	\$3, \$15;			# read processor ID register;\\n" \\
" 	li		\$2, 0x6303;				#godson2f prid;\\n" \\
" 	beq		\$2, \$3, godson_2f;\\n" \\
" 	nop;\\n" \\
"	li		\$2, 0x6302;				#godson2e prid;\\n" \\
"	bne     \$2, \$3,11f;             #godson3a/2g need not flush\\n" \\
"	nop;\\n" \\
"# godson2e;\\n" \\
" godson_2f: " \\
"	li	  \$2, 0x80000000;\\n" \\
"   addu  \$3,\$2,512*1024;\\n" \\
"10:\\n" \\
"	cache	3, 0(\$2);\\n" \\
"	cache	3, 1(\$2);\\n" \\
"	cache	3, 2(\$2);\\n" \\
"	cache	3, 3(\$2);\\n" \\
"	addu	\$2, 32;\\n" \\
"	bne	    \$2,\$3, 10b;\\n" \\
"	nop;\\n" \\
"11:\\n" \\
:::"\$2","\$3"
);
}
#else
void flush_cache()
{

#ifndef WAYBIT
#define WAYBIT 0
#endif
#define WAY__(x) #x
#define WAY_(x,y) WAY__((x<<y))
#define WAY(x) WAY_(x,WAYBIT)
asm volatile(\
"		.set mips3;\\n" 
"        li    \$5,0x80000000;\\n" 
"        addu  \$6,\$5,16384;\\n" 
"1:\\n" 
"        cache  1," WAY(0) "(\$5);\\n" 
"        cache  1," WAY(1) "(\$5);\\n" 
"        cache  1," WAY(2) "(\$5);\\n" 
"        cache  1," WAY(3) "(\$5);\\n" 
"        cache  0," WAY(0) "(\$5);\\n" 
"        cache  0," WAY(1) "(\$5);\\n" 
"        cache  0," WAY(2) "(\$5);\\n" 
"        cache  0," WAY(3) "(\$5);\\n" 
"        add    \$5,\$5,32;\\n" 
"        bne    \$5,\$6,1b;\\n" 
"        nop;\\n" 
"		.set mips0;\\n" 
::: "\$5","\$6");
}
#endif
void initmips(unsigned long long msize,unsigned long long dmsize, unsigned long long dctrl)
{
    long *edata=(void *)$myedata;
    long *end=(void *)$myend;
    int *p;

	int debug=(msize==0);
#ifdef LS3A2H_STR
    long long str_ra,str_flag,str_sp;
    str_ra = *((long long*)0xafaaa040);
    str_sp = *((long long*)0xafaaa048);
    str_flag = *((long long*)0xafaaa050);
#endif 
//	CPU_TLBClear();
    stringserial("Uncompressing Bios");
    if(!debug||dctrl&1)enable_cache();
	
#ifdef LS3A2H_STR
    if ((str_sp < 0x9800000000000000) || (str_ra < 0xffffffff80000000)
		    || (str_flag != 0x5a5a5a5a5a5a5a5a)) {
#endif
	    while(1)
	    {
		    if(run_unzip(biosdata,$mystart)>=0)break;
	    }
#ifdef LS3A2H_STR
    }
#endif	
    stringserial("OK,Booting Bios\\r\\n");
    for(p=edata;p<=end;p++)
    {
        *p=0;
    }
	memset($mystart-0x1000,0,0x1000);//$mystart-0x1000 for frame(registers),memset for pretty
#ifdef NOCACHE2
	flush_cache();
#else
	flush_cache2();
#endif
    realinitmips(debug?dmsize:msize);
}


void realinitmips(unsigned long long msize)
{
    stringserial("zloader/initmips.c realinitmips ...\\r\\n");

	     asm ("li  \$29,$mystart-0x4000;\\n" \\
"		       li \$2,$myinitmips;\\n" \\
"			   move \$4,\%0;\\n" \\
"			   jalr \$2;\\n" \\
"			   nop;\\n" \\
"			  1: b 1b;nop;" \\
          :
          : "r" (msize)
          : "\$29", "\$2","\$4");

}
END
);

make dtb 对应的过程

dtb:
	make -C ../Targets/${TARGET}/compile/${TARGETEL}/ DTB_O=`pwd`/${TARGET}.dtb.i DTB_I=`pwd`/../Targets/${TARGET}/conf/${TARGET}.dts dtb
	./dtc -I dts -O dtb -o ${TARGET}.dtb  ${TARGET}.dtb.i
	( echo  "#include <include/load_dtb.h>";echo NVRAM_OFFS; echo DTB_OFFS; )| make -C ../Targets/${TARGET}/compile/${TARGETEL}/ DTB_O=`pwd`/dtbinfo.txt DTB_I=- dtb
	[ -f gzrom.bin ] && cp gzrom.bin gzrom-dtb.bin && python ../tools/pmonenv.py -O $$((`tail -n 1 dtbinfo.txt`)) -o $$((`tail -n 2 dtbinfo.txt|head -n 1`)) -f gzrom-dtb.bin -d ${TARGET}.dtb -w  al=\(usb0,0\)/boot/vmlinuz al1=\(wd0,0\)/boot/vmlinuz append="'console=ttyS0,115200 console=tty initcall_debug=1 loglevel=20'" FR=1

进入Targets/LS2K/compile/ls2k,以Targets/LS2K/conf/ls2k.dts为输入文件,LS2K.dtb.i为输出文件,执行make dtb
通过dtcLS2K.dtb.i转换成LS2K.dtb
通过../tools/pmonenv.pydtb和内核启动参数追加到gzrom.bin生成gzrom-dtb.bin

make dtb 的编译规则(根目录下的Makefile.inc):

dtb:
    ${CC} ${AFLAGS} ${CPPFLAGS}  -E  -D__ASSEMBLY__ -D__DTS__ -x assembler-with-cpp -o $(DTB_O) $(DTB_I)

tools/pmonenv.py

$ cat  ../tools/pmonenv.py 
"""
python pmonenv.py -f gzrom.bin -o 0x70000 -s 512 al=/dev/mtd0 append="'root=/dev/mtdblock0'"
python ../tools/pmonenv.py -f gzrom-dtb.bin -d ls2k.dtb -w  al=/dev/ram@p0x110000000 al1=/dev/ram@p0x110000000 append="'console=ttyS0,115200 initcall_debug=1 loglevel=20 nosmp'" FR=1
"""
import struct
import sys
import getopt
def readenv(fname,foff,fsz,argv):
    f=open(fname,'rb+')
    f.seek(foff,0)
    a=f.read(fsz);
    a=a.ljust(fsz,b'\x00')
    f.close()
    d={}
    b = struct.unpack('!'+str(len(a)//2)+'H',a)
    if(sum(b)&0xffff):
     print('checksum error, rebuild env')
     t = argv
    else:
     e = a[2:].find(b'\x00\x00')
     t = a[2:2+e].split(b'\x00')+list(map(lambda x:x.encode('utf8'),argv))
    for i in t:
       a=i.split(b'=',1)
       if len(a) > 1:
         d[a[0]] = a[1]
       elif a[0] in d:
         del d[a[0]]
    return d

def writeenv(fname,foff,fsz,d):
    
    a=b'\x00\x00'
    for i in d.keys():
     a += i+b'='+d[i]+b'\x00'
    a=a.ljust(fsz,b'\x00')
    
    b = struct.pack('!H',(-sum(struct.unpack('!'+str(len(a)//2)+'H',a)))&0xffff)
    a=b+a[2:]
    
    f=open(fname,'rb+')
    f.seek(foff,0)
    f.write(a)
    f.close()

def writehexenv(fname,hexbin):
    f=open(fname,'rb+')
    f.seek(foff+fsz, 0)
    f.write('\xff'*256)
    for b in hexbin.split(','):
      i,v = b.split(':')
      f.seek(foff+int(i,0),0)
      f.write(v.decode('hex'))
    f.close()

def writedtb(fname,dtb,foff):
    f=open(dtb,'rb')
    a=f.read();
    f.close()
    a=a.ljust(0x4000-8,'\x00')
    b = struct.pack('I',(-sum(struct.unpack(''+str(len(a)//4)+'I',a)))&0xffffffff)
    a=b+a+b
    
    f=open(fname,'rb+')
    f.seek(foff-0x4000,0)
    f.write(a)
    f.close()
    
if __name__ == '__main__':
    opt,argv=getopt.getopt(sys.argv[1:],'b:o:s:f:wd:')
    opt=dict(opt)
    foff = int(opt['-o'],0) if '-o' in opt  else 0x000ff000
    fsz = int(opt['-s'],0) if '-s' in opt else 500
    fname = opt['-f'] if '-f' in opt else 'gzrom.bin'
    
    d=readenv(fname,foff,fsz,argv)
    print(d)
    if '-w' in opt:
     writeenv(fname,foff,fsz,d)
     if '-b' in opt: writehexenv(fname, opt['-b'])
     if '-d' in opt: writedtb(fname, opt['-d'], foff)
           

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值