setup.S分析

1. setup.S

setup.S is responsible for getting the system data from the BIOS and putting them into appropriate places in system memory.

kernel version : 2.6.11.12
grub version:0.97
kernel elf type: bzImage

1.1 Header

/* Signature words to ensure LILO loaded us right */
#define SIG1    0xAA55
#define SIG2    0x5A5A

INITSEG  = DEF_INITSEG          # 0x9000, we move boot here, out of the way
SYSSEG   = DEF_SYSSEG           # 0x1000, system loaded at 0x10000 (65536).
SETUPSEG = DEF_SETUPSEG         # 0x9020, this is the current segment
                                # ... and the former contents of CS

DELTA_INITSEG = SETUPSEG - INITSEG      # 0x0020
/* Signature words to ensure LILO loaded us right */
#define SIG1    0xAA55
#define SIG2    0x5A5A

INITSEG  = DEF_INITSEG          # 0x9000, we move boot here, out of the way
SYSSEG   = DEF_SYSSEG           # 0x1000, system loaded at 0x10000 (65536).
SETUPSEG = DEF_SETUPSEG         # 0x9020, this is the current segment
                                # ... and the former contents of CS

DELTA_INITSEG = SETUPSEG - INITSEG      # 0x0020
.code16
.globl begtext, begdata, begbss, endtext, enddata, endbss

.text
begtext:
.data
begdata:
.bss
begbss:
.text
start:
        jmp     trampoline

# This is the setup header, and it must start at %cs:2 (old 0x9020:2)

                .ascii  "HdrS"          # header signature
                .word   0x0203          # header version number (>= 0x0105)
                                        # or else old loadlin-1.5 will fail)
realmode_swtch: .word   0, 0            # default_switch, SETUPSEG
start_sys_seg:  .word   SYSSEG
                .word   kernel_version  # pointing to kernel version string
                                        # above section of header is compatible
                                        # with loadlin-1.5 (header v1.5). Don't
                                        # change it.

type_of_loader: .byte   0               # = 0, old one (LILO, Loadlin,
                                        #      Bootlin, SYSLX, bootsect...)
                                        # See Documentation/i386/boot.txt for
                                        # assigned ids

# flags, unused bits must be zero (RFU) bit within loadflags
loadflags:
LOADED_HIGH     = 1                     # If set, the kernel is loaded high
CAN_USE_HEAP    = 0x80                  # If set, the loader also has set
                                        # heap_end_ptr to tell how much
                                        # space behind setup.S can be used for
                                        # heap purposes.
                                        # Only the loader knows what is free
#ifndef __BIG_KERNEL__
                .byte   0
#else
                .byte   LOADED_HIGH
#endif
setup_move_size: .word  0x8000          # size to move, when setup is not
                                        # loaded at 0x90000. We will move setup
                                        # to 0x90000 then just before jumping
                                        # into the kernel. However, only the
                                        # loader knows how much data behind
                                        # us also needs to be loaded.

code32_start:                           # here loaders can put a different
                                        # start address for 32-bit code.
#ifndef __BIG_KERNEL__
                .long   0x1000          #   0x1000 = default for zImage
#else
                .long   0x100000        # 0x100000 = default for big kernel
#endif

ramdisk_image:  .long   0               # address of loaded ramdisk image
                                        # Here the loader puts the 32-bit
                                        # address where it loaded the image.
                                        # This only will be read by the kernel.

ramdisk_size:   .long   0               # its size in bytes

bootsect_kludge:
                .long   0               # obsolete

heap_end_ptr:   .word   modelist+1024   # (Header version 0x0201 or later)
                                        # space from here (exclusive) down to
                                        # end of setup code can be used by setup
                                        # for local heap purposes.

pad1:           .word   0
cmd_line_ptr:   .long 0                 # (Header version 0x0202 or later)
                                        # If nonzero, a 32-bit pointer
                                        # to the kernel command line.
                                        # The command line should be
                                        # located between the start of
                                        # setup and the end of low
                                        # memory (0xa0000), or it may
                                        # get overwritten before it
                                        # gets read.  If this field is
                                        # used, there is no longer
                                       # anything magical about the
                                        # 0x90000 segment; the setup
                                        # can be located anywhere in
                                        # low memory 0x10000 or higher.       
ramdisk_max:    .long (-__PAGE_OFFSET-(512 << 20)-1) & 0x7fffffff
                                        # (Header version 0x0203 or later)
                                        # The highest safe address for
                                        # the contents of an initrd

trampoline:     call    start_of_setup
                .align 16
                                        # The offset at this point is 0x240
                .space  (0x7ff-0x240+1) # E820 & EDD space (ending at 0x7ff)
# End of setup header #####################################################                                                                                                                     

The __PAGE_OFFSET definition in linux/asm-i386/page.h

#define __PAGE_OFFSET           (0xC0000000)

The setup header must follow some layout pattern. Refer to linux/Documentation/i386/boot.txt:

The header looks like:

Offset  Proto   Name            Meaning
/Size

01F1/1  ALL     setup_sects     The size of the setup in sectors
01F2/2  ALL     root_flags      If set, the root is mounted readonly
01F4/2  ALL     syssize         DO NOT USE - for bootsect.S use only
01F6/2  ALL     swap_dev        DO NOT USE - obsolete
01F8/2  ALL     ram_size        DO NOT USE - for bootsect.S use only
01FA/2  ALL     vid_mode        Video mode control
01FC/2  ALL     root_dev        Default root device number
01FE/2  ALL     boot_flag       0xAA55 magic number
0200/2  2.00+   jump            Jump instruction
0202/4  2.00+   header          Magic signature "HdrS"
0206/2  2.00+   version         Boot protocol version supported
0208/4  2.00+   realmode_swtch  Boot loader hook (see below)
020C/2  2.00+   start_sys       The load-low segment (0x1000) (obsolete)
020E/2  2.00+   kernel_version  Pointer to kernel version string
0210/1  2.00+   type_of_loader  Boot loader identifier
0211/1  2.00+   loadflags       Boot protocol option flags
0212/2  2.00+   setup_move_size Move to high memory size (used with hooks)
0214/4  2.00+   code32_start    Boot loader hook (see below)
0218/4  2.00+   ramdisk_image   initrd load address (set by boot loader)
021C/4  2.00+   ramdisk_size    initrd size (set by boot loader)
0220/4  2.00+   bootsect_kludge DO NOT USE - for bootsect.S use only
0224/2  2.01+   heap_end_ptr    Free memory after setup end
0226/2  N/A     pad1            Unused
0228/4  2.02+   cmd_line_ptr    32-bit pointer to the kernel command line
022C/4  2.03+   initrd_addr_max Highest legal initrd address

1.2 Check Code Integrity

/
trampoline:     call    start_of_setup
                .align 16
                                        # The offset at this point is 0x240
                .space  (0x7ff-0x240+1) # E820 & EDD space (ending at 0x7ff)       
//        

///
start_of_setup:
# Bootlin depends on this being done early
        movw    $0x01500, %ax
        movb    $0x81, %dl
        int     $0x13
        //   int  13h/AH=15h----Get Disk Type   return :AH=1,loppy without change-line support,value CX:DX when AL=3h
        
#ifdef SAFE_RESET_DISK_CONTROLLER
# Reset the disk controller.
        movw    $0x0000, %ax
        movb    $0x80, %dl
        int     $0x13
        //int 13/AH=00h: DISK - RESET DISK SYSTEM
#endif

# Set %ds = %cs, we know that SETUPSEG = %cs at this point
        movw    %cs, %ax                # aka SETUPSEG
        movw    %ax, %ds
# Check signature at end of setup
        cmpw    $SIG1, setup_sig1
        jne     bad_sig

        cmpw    $SIG2, setup_sig2
        jne     bad_sig

        jmp     good_sig1
# Routine to print asciiz string at ds:si
prtstr:
        lodsb
        andb    %al, %al
        jz      fin

        call    prtchr
        jmp     prtstr

fin:    ret

Signature is checked to verify code integrity.

If signature is not found, the rest setup code may precede vmlinux at SYSSEG:0.

bad_sig:
        movw    %cs, %ax                        # SETUPSEG
        subw    $DELTA_INITSEG, %ax             # INITSEG
        movw    %ax, %ds
        xorb    %bh, %bh
        movb    (497), %bl                      # get setup sect from bootsect
        subw    $4, %bx                         # LILO loads 4 sectors of setup
        shlw    $8, %bx                         # convert to words (1sect=2^8 words)
        movw    %bx, %cx
        shrw    $3, %bx                         # convert to segment
        addw    $SYSSEG, %bx
        movw    %bx, %cs:start_sys_seg
# Move rest of setup code/data to here
        movw    $2048, %di                      # four sectors loaded by LILO
        subw    %si, %si
        pushw   %cs
        popw    %es
        movw    $SYSSEG, %ax
        movw    %ax, %ds
        rep
        movsw     //source address DS:SI     DS=$SYSSEG=0x1000  SI=0
        movw    %cs, %ax                        # aka SETUPSEG
        movw    %ax, %ds
        cmpw    $SIG1, setup_sig1
        jne     no_sig

        cmpw    $SIG2, setup_sig2
        jne     no_sig

        jmp     good_sig
no_sig:
        lea     no_sig_mess, %si
        call    prtstr

no_sig_loop:
        hlt
        jmp     no_sig_loop

The setup code has been moved to correct place. Variable start_sys_seg points to where real system code starts. If “bad_sig” does not happen, start_sys_seg remains SYSSEG.

4.3 Check Loader type

# Check if an old loader tries to load a big-kernel
        testb   $LOADED_HIGH, %cs:loadflags     # Do we have a big kernel?
        jz      loader_ok                       # No, no danger for old loaders.

        cmpb    $0, %cs:type_of_loader          # Do we have a loader that
                                                # can deal with us?
        jnz     loader_ok                       # Yes, continue.

loadflags

//  define in setup.S
loadflags:
LOADED_HIGH     = 1                     # If set, the kernel is loaded high
CAN_USE_HEAP    = 0x80                  # If set, the loader also has set

//modify in grub
stage2/shared.h:#define LINUX_FLAG_CAN_USE_HEAP		0x80
stage2/boot.c:	      lh->loadflags |= LINUX_FLAG_CAN_USE_HEAP;

loadflags=0x1|0x80=0x81
type_of_loader

//  define in setup.S
type_of_loader: .byte   0

// modify in grub
stage2/shared.h:#define LINUX_BOOT_LOADER_TYPE		0x71
stage2/boot.c:	  lh->type_of_loader = LINUX_BOOT_LOADER_TYPE;

type_of_loader=0x71

Note that type_of_loader has been changed to 0x71, loadflags has changed to 0x81。

1.4 Get Memory Size

Try three different memory detection schemes to get the extended memory size (above 1M) in KB.
First, try e820h, which lets us assemble a memory map; then try e801h, which returns a 32-bit memory size; and finally 88h, which returns 0-64M.
目的:如果e820得不到,这可以从e801得到,最后从88h中得到。

loader_ok:
# Get memory size (extended mem, kB)

	xorl	%eax, %eax
	movl	%eax, (0x1e0)
#ifndef STANDARD_MEMORY_BIOS_CALL
	movb	%al, (E820NR)
# Try three different memory detection schemes.  First, try
# e820h, which lets us assemble a memory map, then try e801h,
# which returns a 32-bit memory size, and finally 88h, which
# returns 0-64m

# method E820H:
# the memory map from hell.  e820h returns memory classified into
# a whole bunch of different types, and allows memory holes and
# everything.  We scan through this memory map and build a list
# of the first 32 memory areas, which we return at [E820MAP].
# This is documented at http://www.acpi.info/, in the ACPI 2.0 specification.

#define SMAP  0x534d4150

meme820:
	xorl	%ebx, %ebx			# continuation counter
	movw	$E820MAP, %di			# point into the whitelist
						# so we can have the bios
						# directly write into it.

jmpe820:
	movl	$0x0000e820, %eax		# e820, upper word zeroed
	movl	$SMAP, %edx			# ascii 'SMAP'
	movl	$20, %ecx			# size of the e820rec
	pushw	%ds				# data record.
	popw	%es
	int	$0x15				# make the call
	jc	bail820				# fall to e801 if it fails

	cmpl	$SMAP, %eax			# check the return is `SMAP'
	jne	bail820				# fall to e801 if it fails

#	cmpl	$1, 16(%di)			# is this usable memory?
#	jne	again820

	# If this is usable memory, we save it by simply advancing %di by
	# sizeof(e820rec).
	#
good820:
	movb	(E820NR), %al			# up to 32 entries
	cmpb	$E820MAX, %al
	jnl	bail820

	incb	(E820NR)
	movw	%di, %ax
	addw	$20, %ax
	movw	%ax, %di
again820:
	cmpl	$0, %ebx			# check to see if
	jne	jmpe820				# %ebx is set to EOF
bail820:


# method E801H:
# memory size is in 1k chunksizes, to avoid confusing loadlin.
# we store the 0xe801 memory size in a completely different place,
# because it will most likely be longer than 16 bits.
# (use 1e0 because that's what Larry Augustine uses in his
# alternative new memory detection scheme, and it's sensible
# to write everything into the same place.)

meme801:
	stc					# fix to work around buggy
	xorw	%cx,%cx				# BIOSes which dont clear/set
	xorw	%dx,%dx				# carry on pass/error of
						# e801h memory size call
						# or merely pass cx,dx though
						# without changing them.
	movw	$0xe801, %ax
	int	$0x15
	jc	mem88

	cmpw	$0x0, %cx			# Kludge to handle BIOSes
	jne	e801usecxdx			# which report their extended
	cmpw	$0x0, %dx			# memory in AX/BX rather than
	jne	e801usecxdx			# CX/DX.  The spec I have read
	movw	%ax, %cx			# seems to indicate AX/BX 
	movw	%bx, %dx			# are more reasonable anyway...

e801usecxdx:
	andl	$0xffff, %edx			# clear sign extend
	shll	$6, %edx			# and go from 64k to 1k chunks
	movl	%edx, (0x1e0)			# store extended memory size
	andl	$0xffff, %ecx			# clear sign extend
 	addl	%ecx, (0x1e0)			# and add lower memory into
						# total size.

# Ye Olde Traditional Methode.  Returns the memory size (up to 16mb or
# 64mb, depending on the bios) in ax.
mem88:

#endif
	movb	$0x88, %ah
	int	$0x15
	movw	%ax, (2)

1.5检查硬件

1.5.1设置键盘速率为最大

# Set the keyboard repeat rate to the max
	movw	$0x0305, %ax
	xorw	%bx, %bx
	int	$0x16

1.5.2 设置video参数

# Check for video adapter and its parameters and allow the
# user to browse video modes.
	call	video				# NOTE: we need %ds pointing
						# to bootsector

1.5.3 检查键盘参数并保存到ES:DI(0x9000:0x80)

# Get hd0 data...
	xorw	%ax, %ax
	movw	%ax, %ds
	ldsw	(4 * 0x41), %si
	movw	%cs, %ax			# aka SETUPSEG
	subw	$DELTA_INITSEG, %ax		# aka INITSEG
	pushw	%ax
	movw	%ax, %es
	movw	$0x0080, %di
	movw	$0x10, %cx
	pushw	%cx
	cld
	rep
 	movsb
# Get hd1 data...
	xorw	%ax, %ax
	movw	%ax, %ds
	ldsw	(4 * 0x46), %si
	popw	%cx
	popw	%es
	movw	$0x0090, %di
	rep
	movsb
# Check that there IS a hd1 :-)
	movw	$0x01500, %ax
	movb	$0x81, %dl
	int	$0x13
	jc	no_disk1
	
	cmpb	$3, %ah
	je	is_disk1

no_disk1:
	movw	%cs, %ax			# aka SETUPSEG
	subw	$DELTA_INITSEG, %ax 		# aka INITSEG
	movw	%ax, %es
	movw	$0x0090, %di
	movw	$0x10, %cx
	xorw	%ax, %ax
	cld
	rep
	stosb
is_disk1:

#endif

检查 PS/2 设备,如果存在,这在0x901ff地址中

   movw	%cs, %ax			# aka SETUPSEG
	subw	$DELTA_INITSEG, %ax		# aka INITSEG
	movw	%ax, %ds
	movw	$0, (0x1ff)			# default is no pointing device
	int	$0x11				# int 0x11: equipment list
	testb	$0x04, %al			# check if mouse installed
	jz	no_psmouse

	movw	$0xAA, (0x1ff)			# device present
no_psmouse:

得到IST的值,分别存在0x90096,0x90100,0x90104,0x90108处

	movl	$0x0000E980, %eax		# IST Support 
	movl	$0x47534943, %edx		# Request value
	int	$0x15

	movl	%eax, (96)
	movl	%ebx, (100)
	movl	%ecx, (104)
	movl	%edx, (108)

APM保存BIOS相关信息

#if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
# Then check for an APM BIOS...
						# %ds points to the bootsector
	movw	$0, 0x40			# version = 0 means no APM BIOS
	movw	$0x05300, %ax			# APM BIOS installation check
	xorw	%bx, %bx
	int	$0x15
	jc	done_apm_bios			# Nope, no APM BIOS
	
	cmpw	$0x0504d, %bx			# Check for "PM" signature
	jne	done_apm_bios			# No signature, no APM BIOS

	andw	$0x02, %cx			# Is 32 bit supported?
	je	done_apm_bios			# No 32-bit, no (good) APM BIOS

	movw	$0x05304, %ax			# Disconnect firstpb  just in case
	xorw	%bx, %bx
	int	$0x15				# ignore return code
	movw	$0x05303, %ax			# 32 bit connect
	xorl	%ebx, %ebx
	xorw	%cx, %cx			# paranoia :-)
	xorw	%dx, %dx			#   ...
	xorl	%esi, %esi			#   ...
	xorw	%di, %di			#   ...
	int	$0x15
	jc	no_32_apm_bios			# Ack, error. 

	movw	%ax,  (66)			# BIOS code segment
	movl	%ebx, (68)			# BIOS entry point offset
	movw	%cx,  (72)			# BIOS 16 bit code segment
	movw	%dx,  (74)			# BIOS data segment
	movl	%esi, (78)			# BIOS code segment lengths
	movw	%di,  (82)			# BIOS data segment length
# Redo the installation check as the 32 bit connect
# modifies the flags returned on some BIOSs
	movw	$0x05300, %ax			# APM BIOS installation check
	xorw	%bx, %bx
	xorw	%cx, %cx			# paranoia
	int	$0x15
	jc	apm_disconnect			# error -> shouldn't happen

	cmpw	$0x0504d, %bx			# check for "PM" signature
	jne	apm_disconnect			# no sig -> shouldn't happen

	movw	%ax, (64)			# record the APM BIOS version
	movw	%cx, (76)			# and flags
	jmp	done_apm_bios

apm_disconnect:					# Tidy up
	movw	$0x05304, %ax			# Disconnect
	xorw	%bx, %bx
	int	$0x15				# ignore return code

	jmp	done_apm_bios

no_32_apm_bios:
	andw	$0xfffd, (76)			# remove 32 bit support bit
done_apm_bios:
#endif

说明:

  1. 把BIOS的数据段地址,代码段地址保存到地址0x90066,0x90068,0x90072,0x90074,0x90078,0x90082

  2. 把BIOS version信息保存到0x90064,0x90076中

    movw %ax, (66) # BIOS code segment
    movl %ebx, (68) # BIOS entry point offset
    movw %cx, (72) # BIOS 16 bit code segment
    movw %dx, (74) # BIOS data segment
    movl %esi, (78) # BIOS code segment lengths
    movw %di, (82) # BIOS data segment length

返回值分析

int 15h/eax=0xe820

E820MAP定义在文件include/asm/e820.h:
#define E820MAP 0x2d0
e820的返回值存放在E820MAP地址中,e801的返回值存放在0x1e0中,88h的返回值2中。
e820 memory

int 15h/eax=0xe801

ax=0x3c00   // extended memory between 1M and 16M, in K   15M
bx=0x100   //extended memory above 16M, in 64K blocks      16M
cx=0x3c00  //configured memory 1M to 16M, in K                    15M
dx=0x100  //configured memory above 16M, in 64K blocks     16M

        andl    $0xffff, %edx                   # clear sign extend
        shll    $6, %edx                        # and go from 64k to 1k chunks
        movl    %edx, (0x1e0)                   # store extended memory size
        andl    $0xffff, %ecx                   # clear sign extend
        addl    %ecx, (0x1e0)                   # and add lower memory into
                                                # total size.
   //ds:0x1e0 store value  extended momory size in K,bx<<6+ax , 31M in this example

int 15h/eax=0x88

eax=0x7c00   //number of contiguous KB starting at absolute address 100000h; 31M
movw    %ax, (2) //store value to ds:2          *(0x90002)=0x7c00

int15/AH=0xC0---->Get system configuration

存放在ds:0xa0中

0x000900a0 <bogus+       0>:	0x08	0x00	0xfc	0x00	0x01	0x74	0x40	0x00
0x000900a8 <bogus+       8>:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00

model=oxfc submodel=0x00 version=0x1–>2nd

ModelSubmdlRevBIOS dateSystem
FCh00h01h06/10/85AT model 239 6 MHz 30MB

从上面知道:BIOS date:06/10/85 System: AT model 239 6MHz 30MB
0x74支持下面功能:
Bit(s) Description (Table 00510)
6 2nd interrupt controller (8259) installed
5 Real-Time Clock installed
4 INT 15/AH=4Fh called upon INT 09h
2 extended BIOS area allocated (usually at top of RAM)
0x4支持下面功能:
Bit(s) Description
6 INT 16/AH=09h (keyboard functionality) supported (see #00585)

int11h: BIOS - GET EQUIPMENT LIST

eax=0x13134226

Bit(s) Description(Table 00226)
180x87 coprocessor installed
2pointing device installed (PS)
5-4initial video mode. 10 80x25 color.
11-9number of serial ports installed 1个串口
15-14number of parallel ports installed 1个并口

硬盘参数

int 0x41 的中断向量位置(4 * 0x41 =0x0000:0x0104)存放的并不是中断程序的地址而是第一个硬盘的基本参数表的地址

位移大小说明
0x00柱面数
0x02字节磁头数
0x03开始减小写电流的柱面(仅PC XT 使用,其它为0)
0x05开始写前预补偿柱面号(乘4)
0x07字节最大ECC 猝发长度(仅XT 使用,其它为0)
0x08字节控制字节(驱动器步进选择)位0 未用 位1 保留(0) (关闭IRQ) 位2 允许复位位3 若磁头数大于8 则置1 位4 未用(0) 位5 若在柱面数+1 处有生产商的坏区图,则置1 位6 禁止ECC 重试 位7 禁止访问重试。
0x09字节标准超时值(仅XT 使用,其它为0)
0x0A字节格式化超时值(仅XT 使用,其它为0)
0x0B字节检测驱动器超时值(仅XT 使用,其它为0)
0x0C磁头着陆(停止)柱面号
0x0E字节每磁道扇区数
0x0F字节保留

在0x90104处,通过rep搬移到0x90080处。
本实验磁盘的硬盘信息
0x00090080 <bogus+ 0>: 0x64 0x00 0x10 0x00 0x00 0xff 0xff 0x00
0x00090088 <bogus+ 8>: 0xc8 0x00 0x00 0x00 0x64 0x00 0x3f 0x00
柱面数: 0x64=100
磁头数: 0x10=16
每磁道扇区: 0x3f=63

int15/EAX=0xe980/EDX=47534943–> intel speed step (IST)

输出含义
eax信号值(signature)
ebx命令(command)
ecx事件(evetn)
edx功耗登记(perf_level)

键盘参数

repeat delay:
The pause between pressing the key and when it starts repeating is the repeat delay. (重复延迟就是你按一个键不松,多长时间后会再次重复这个字符)
repeat rate:
After you press and hold down a key on the keyboard, the key starts repeating itself. The speed at which it repeats is the repeat rate.(速率就是当开始重复以后,每隔多长时间重复一次)

参考

Int 13/AH=15h–>Get Disk type

linux/arch/i386/boot/setup.S

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值