2440init代码分析(4)

310 ;===========================================================
311 	
312 	ldr	r0, =BWSCON
313 	ldr	r0, [r0]
314 	ands	r0, r0, #6		;OM[1:0] != 0, NOR FLash boot
315 	bne	copy_proc_beg		;do not read nand flash
316 	adr	r0, ResetEntry		;OM[1:0] == 0, NAND FLash boot
317 	cmp	r0, #0				;if use Multi-ice, 
318 	bne	copy_proc_beg		;do not read nand flash for boot
319 	;nop
320 ;===========================================================

第312-318行判断代码启动的方式,是通过BWSCON寄存器的[2:1]两位,这两位是OM[1:0]的显示,如果OM[1:0]为00,是nandflash启动方式,如果为11就是test mode,,再者就是NORFLASH方式。分别介绍一下这三种启动方式。

第一种是nandboot,由于nandflash的物理结构,代码是不能在nandflash中执行的,所以硬件会自动把代码的前4K数据放到SRAM中执行,然后再把整个代码拷贝到sdram中。由于代码启动时为在0x0地址处运行,所以这个4K的地址被映射到0x0处。

第二种是norflash启动,代码端可以在norflash中执行,由于RW段在norflash中操作起来相当耗时,所以代码会搬到SDRAM中执行。由于norflash片在0x0的地址处,所以不需要映射地址。

第三种是使用调试工具,使代码直接在SDRAM中执行。

312-314行把BWSCON寄存器的地址放到R0中,再把R0中的值取出,放到R0中,与#6相与的结果放到R0

315bne指令不相等是跳转,B是跳转,NE是不相等(或不为0)。也就BWSCON[2:1]两位不为0是跳转到copy_proc_beg.

316-318行把入RsetEntry的值放到R0中,这个值可能是0,也可能是0x30000000,主要看启动的方式,如果使用调试工具的话,这个值为0x30000000.如果R0中的值不为0,再跳copy_proc_beg函数处,否则的话,则执行下面的代码,也就是nandflash启动。

/*在介绍nand boot之前,先说一下,nandflash的基本知识,

第一部分,nandflash的概述。

nandflash是由planeblockpage组成,page是读写的最小单位,block是擦除的最小单位。每一个页分成两部分,一部分是用来存放数据,另一部分是用存放OOB(可以存放纠错的信息)Flash中的地址分成两部分,一部分为column address(页内地址),例如512Byte大小的页,页内地址为9位。另一部分为页地址,例如64M大小的DIVICE,Block大小16K,PAGE大小512,则PAGE count = 64M/16K*16K/512=1024*64=2^17。需要17位。

目前使用的K9F1208的情况

Memory SIZE = (64M+2K)*8bit

Block SIZE = (16K+512)Byte

Page SIZE = (512+16)byte

Block Count =64M/16K = 4096

Page Count = 16K/512 = 32

第二部分,nandflash的读写

读写的过程,先发送命令和地址,后面会跟着读写的数据,数据由flash controlflash内部的数据缓冲区,然后再写到指定的flash地址上或者先到FLASH内部的缓冲区,再到用户的缓冲区。例如K9F1208读数据过程 write命令(00H)-----write(address)---write(30H)---数据到内部缓冲区(busy)-----Data out0….Data OutN.

对于下面这段代码,感觉用C语言写比较直观一下。

*

*/      

321 nand_boot_beg
322 	[ {TRUE}
323 	bl CopyProgramFromNand
324 	|
325 	mov	r5, #NFCONF
326 	;set timing value
327 	ldr	r0,	=(7<<12)|(7<<8)|(7<<4)
328 	str	r0,	[r5]
329 	;enable control
330 	ldr	r0, =(0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(1<<6)|(1<<5)|(1<<4)|(1<<1)|(1<<0)
331 	str	r0, [r5, #4]
332 	

325-331行主要是配置nand flash的控制时序,和对nand flash控制寄存器的设置

327行需要设置 taclstwrph0,twrph1.它们三个之和只要大于50NS即可,使用的HCLK的时间为100M,也就是一个时间周期为10NS

Tacls的值Tacls * Hclk = 0*10NS

Twrph0的值Twrph0 *5 =50NS

Twrph1的值Twrph1*1 =10NS

328行把配置的值写到NFCONF寄存器中

330-331行查看datasheet page226     

333 	bl	ReadNandID
334 	mov	r6, #0
335 	ldr	r0, =0xec73
336 	cmp	r5,	r0
337 	beq	%F1
338 	ldr	r0, =0xec75
339 	cmp	r5, r0
340 	beq	%F1
341 	mov	r6, #1
342 1

333-342行读出nandflashID,用来判断是什么型号的ID,如果这个有parameter命令的话会更好,因为可以知道几个deviceplaneblockpage、的大小。代码很简单,读出ID和这个两个型号比较。   

343 	bl	ReadNandStatus
344

343read status函数,用来返回flash的状态。(read or busy or fail)

345 	mov	r8, #0
346 	ldr	r9, =ResetEntry
347 2	
348 	ands	r0, r8, #0x1f
349 	bne		%F3
350 	mov		r0, r8
351 	bl		CheckBadBlk
352 	cmp		r0, #0
353 	addne	r8, r8, #32
354 	bne		%F4
355 3	
356 	mov	r0, r8
357 	mov	r1, r9
358 	bl	ReadNandPage
359 	add	r9, r9, #512
360 	add	r8, r8, #1
361 4	
362 	cmp	r8, #5120
363 	bcc	%B2
364 	
365 	mov	r5, #NFCONF			;DsNandFlash
366 	ldr	r0, [r5, #4]
367 	bic r0, r0, #1
368 	str	r0, [r5, #4]
369 	]

345-369行这几行要连起来行,要不然不容易理解,其实本意就是把数据拷到SDRAM中,代码首先给出了拷贝的目的地址(ResetEntry)。然后会check一下,这个块是否为坏块,如果是坏块则R8=R8+32,这个32就是一个块包含的页数,意思就是跳过这个块,然后开始读出flash page中的数据,由于一个页的大小为512Byte。所以R9第次加512,而R8做为计数count,第次递增1,与5120比较。所以拷贝数据的大小为5120*512Byte=2.5M的数据,好像不需要这么多的数据,一个boot的代码也就几百K。不知道为什么.

345-346R80R9拷贝的目的地址。

348-354行如果是第一次读这个块的话,会CHECK块是否为坏块,所以第348R0的值为,不执行跳转,然后把R8的值赋给R0,进行CHECK,如果R0的值不为0,则R8+32,跳转到标号4的地方,说明这是一个bad block

356-360行给R0,R1赋值,进行读数据,读出数据后,R9递增512R8递增1.

362-363行判断是否读完数据

365-368R0写回到NFCONF,R0的最后一位清0,写回到NFCONT中,就是disable flash control.

370 	ldr	pc, =copy_proc_beg
371 ;===========================================================
372 copy_proc_beg
373 	adr	r0, ResetEntry
374 	ldr	r2, BaseOfROM
375 	cmp	r0, r2
376 	ldreq	r0, TopOfROM
377 	beq	InitRam	
378 	ldr r3, TopOfROM
379 0	
380 	ldmia	r0!, {r4-r7}
381 	stmia	r2!, {r4-r7}
382 	cmp	r2, r3
383 	bcc	%B0
384 	
385 	sub	r2, r2, r3
386 	sub	r0, r0, r2				
387 		
388 InitRam	
389 	ldr	r2, BaseOfBSS
390 	ldr	r3, BaseOfZero	
391 0
392 	cmp	r2, r3
393 	ldrcc	r1, [r0], #4
394 	strcc	r1, [r2], #4
395 	bcc	%B0	
396 
397 	mov	r0,	#0
398 	ldr	r3,	EndOfBSS
399 1	
400 	cmp	r2,	r3
401 	strcc	r0, [r2], #4
402 	bcc	%B1
403 	
404 	ldr	pc, =%F2		;goto compiler address
405 2

372-405主要实现代码的拷贝和RAM的初始化,先介绍一下简单的流程,代码先判断ResetEntry和代码运行的地址是否一致,如果相同,说明现在的代码在0x30000000处运行,也就需要初始化RAM并且拷贝RW,ZI段。否则的话就是在NOR FLASH启动。

372-377行把ResetEntry的值放到R0,RO段的运行地址放到R2,并比较它们是否相等,如果相等,则把运行地址放到R0中,同时跳到INITRAM处。

378行如果不相等,则代码在nor flash中,把RO段的长度赋给R3.

379-386行拷贝NOR FLASH中的RO段,LDMIASTMIA是一组寄存器和内存单元传输数据,IA是为事后增加(类似于i++)。把ResetEntry所在的内存中的数据,先放到寄存器中,然后再把寄存器中的值放到R2所指向的内存地址中。直到R2R3的值相等,也就是拷贝整个RO段的大小。

385-386行这两行代码,此时R2==R3,所以R2的值是0,而R0-R2应该是RO段的长度,因为经过拷贝之后,R0的值应该等于R0+R3.

308-405行这段代码就比较简单了,主要实现把RW段,同时进行初始化ZI段。

389-396行此时R0的值为RO的末尾,其实也是RW段的基地址。R3RW段的长度,步长为4,进行搬动,和前面的代码拷贝类似.

307-405行是进行ZI段的初始化,初始化的值为0,R3ZI的大小,执行的步长为4

415 ;===========================================================
416   	; Setup IRQ handler
417 	ldr	r0,=HandleIRQ       ;This routine is needed
418 	ldr	r1,=IsrIRQ	  ;if there is not 'subs pc,lr,#4' at 0x18, 0x1c
419 	str	r1,[r0]
420 

417-419行这几行代码是什么情况,把HandleIRQ的地址给R0,把IsrIRQ的函数地址给R1,然后把R1放到R0寄存器值,其实应该是这样的,中断响应的过程,是会先在一级向量表中查找,如果是IRQ中断,就会在IRQ的二级向量中查找。

421 ;	;Copy and paste RW data/zero initialized data
422 ;	ldr	r0, =|Image$$RO$$Limit| ; Get pointer to ROM data
423 ;	ldr	r1, =|Image$$RW$$Base|  ; and RAM copy
424 ;	ldr	r3, =|Image$$ZI$$Base|
425 ;
426 ;	;Zero init base => top of initialised data
427 ;	cmp	r0, r1      ; Check that they are different
428 ;	beq	%F2
429 ;1
430 ;	cmp	r1, r3      ; Copy init data
431 ;	ldrcc	r2, [r0], #4    ;--> LDRCC r2, [r0] + ADD r0, r0, #4
432 ;	strcc	r2, [r1], #4    ;--> STRCC r2, [r1] + ADD r1, r1, #4
433 ;	bcc	%B1
434 ;2
435 ;	ldr	r1, =|Image$$ZI$$Limit| ; Top of zero init segment
436 ;	mov	r2, #0
437 ;3
438 ;	cmp	r3, r1      ; Zero init
439 ;	strcc	r2, [r3], #4
440 ;	bcc	%B3
441 
442 
443     [ :LNOT:THUMBCODE
444  		bl	Main	;Do not use main() because ......
445  		;ldr	pc, =Main	;
446  		b	.
447     ]
448 
449     [ THUMBCODE	 ;for start-up code for Thumb mode
450  		orr	lr,pc,#1
451  		bx	lr
452  		CODE16
453  		bl	Main	;Do not use main() because ......
454  		b	.
455 		CODE32
456     ]
457 

443-456行两种跳转到Main函数的方式,一种是非thumb代码,一种是thumb代码。

458 
459 ;function initializing stacks
460 InitStacks
461 	;Do not use DRAM,such as stmfd,ldmfd......
462 	;SVCstack is initialized before
463 	;Under toolkit ver 2.5, 'msr cpsr,r1' can be used instead of 'msr cpsr_cxsf,r1'
464 	mrs	r0,cpsr
465 	bic	r0,r0,#MODEMASK
466 	orr	r1,r0,#UNDEFMODE|NOINT
467 	msr	cpsr_cxsf,r1		;UndefMode
468 	ldr	sp,=UndefStack		; UndefStack=0x33FF_5C00
469 
470 	orr	r1,r0,#ABORTMODE|NOINT
471 	msr	cpsr_cxsf,r1		;AbortMode
472 	ldr	sp,=AbortStack		; AbortStack=0x33FF_6000
473 
474 	orr	r1,r0,#IRQMODE|NOINT
475 	msr	cpsr_cxsf,r1		;IRQMode
476 	ldr	sp,=IRQStack		; IRQStack=0x33FF_7000
477 
478 	orr	r1,r0,#FIQMODE|NOINT
479 	msr	cpsr_cxsf,r1		;FIQMode
480 	ldr	sp,=FIQStack		; FIQStack=0x33FF_8000
481 
482 	bic	r0,r0,#MODEMASK|NOINT
483 	orr	r1,r0,#SVCMODE
484 	msr	cpsr_cxsf,r1		;SVCMode
485 	ldr	sp,=SVCStack		; SVCStack=0x33FF_5800
486 
487 	;USER mode has not be initialized.
488 
489 	mov	pc,lr
490 	;The LR register will not be valid if the current mode is not SVC mode.
491 

464-485行分别设置处理器几种工作模式的堆栈,MSR指令用于将通用寄存器的内容或一个立即数传送到状态寄存器(CPSRSPSR)中,MRS指令用于将状态寄存器的内容传送到通用寄存器中。代码大到火类同,只介绍其中的一个。

464行把CPSR的值放到R0中,由于不能直接修改状态寄存器中的值,所以是先读出,修改,再写回。

465BIC指令清除相应的位,在本行代码中MODEMASK的值为0x1f,意思就是清除掉后5位,再放回R0中。

466ORR指令将某些位设成1,在这里面把R0的后5位设成0x1b|0xc0.

467行把R0的值写到CPSR寄存器中,其实只是修改了它的后5位,后5位为0x1b,查看CPSR寄存器的后5位可以得知,处理器的模式为Undefined.

492 ;===========================================================
493 	[ {TRUE}
494 	|
495 ReadNandID
496 	mov      r7,#NFCONF
497 	ldr      r0,[r7,#4]		;NFChipEn();
498 	bic      r0,r0,#2
499 	str      r0,[r7,#4]

496-499行把R0值写入到NFCONF+4的寄存器中,也就NFCONT,使能nandflash controller,然后把第一位取反(bic r0,r0,#2)就是选中nandflash 使能。

500 	mov      r0,#0x90		;WrNFCmd(RdIDCMD);
501 	strb     r0,[r7,#8]
502 	mov      r4,#0			;WrNFAddr(0);
503 	strb     r4,[r7,#0xc]

第500-503行发送命令0x90和地址0写入到NFCMMD(NFCONF+8),NFADDR(NFCONF+0xc)中

504 1							;while(NFIsBusy());
505 	ldr      r0,[r7,#0x20]
506 	tst      r0,#1
507 	beq      %B1

505-507行测试NFSTAT(NFCONF+0x20)的最低位,如果为0,则busy,否则ready.

508 	ldrb     r0,[r7,#0x10]	;id  = RdNFDat()<<8;
509 	mov      r0,r0,lsl #8
510 	ldrb     r1,[r7,#0x10]	;id |= RdNFDat();
511 	orr      r5,r1,r0
512 	ldr      r0,[r7,#4]		;NFChipDs();
513 	orr      r0,r0,#2
514 	str      r0,[r7,#4]
515 	mov		 pc,lr	
516 	

508-515 LDRB字节加载指令,取出NFDATA(NFCONF+0x10)中的1个字节,放到R0第二个8位上,然后再取出1个字节放到低8位上,nandflash片选disable,这个不是nandflash controller.请注意。

517 ReadNandStatus
518 	mov		 r7,#NFCONF
519 	ldr      r0,[r7,#4]		;NFChipEn();
520 	bic      r0,r0,#2
521 	str      r0,[r7,#4]
522 	mov      r0,#0x70		;WrNFCmd(QUERYCMD);
523 	strb     r0,[r7,#8]	
524 	ldrb     r1,[r7,#0x10]	;r1 = RdNFDat();
525 	ldr      r0,[r7,#4]		;NFChipDs();
526 	orr      r0,r0,#2
527 	str      r0,[r7,#4]
528 	mov		 pc,lr
529 

518-528READ STATUS比较简单,和上面的READID,是类似的,先使能NANDFLASH,然后发送命令0x70,把结果放到R1中,然后再取消全能NANDFLASH,返回。

530 WaitNandBusy
531 	mov      r0,#0x70		;WrNFCmd(QUERYCMD);
532 	mov      r1,#NFCONF
533 	strb     r0,[r1,#8]
534 1							;while(!(RdNFDat()&0x40));	
535 	ldrb     r0,[r1,#0x10]
536 	tst      r0,#0x40
537 	beq		 %B1
538 	mov      r0,#0			;WrNFCmd(READCMD0);
539 	strb     r0,[r1,#8]
540 	mov      pc,lr
541 

531-540行因为FLASH在操作过程中,会和生一个busy信号,如READ数据时,如果busy信号产生,说明现在数据正在放FLASH内部的缓冲区放,等放完数据后,这个信号就会变成READY,我们就可以取出数据。

代码很简单,发送命令0x70,然后把返回的数据放到R0中,如果R0的最高位为1,说明现在是FAIL的,不管是读,写都是操作不能完成的。

542 CheckBadBlk
543 	mov		r7, lr
544 	mov		r5, #NFCONF
545 	
546 	bic      r0,r0,#0x1f	;addr &= ~0x1f;
547 	ldr      r1,[r5,#4]		;NFChipEn()
548 	bic      r1,r1,#2
549 	str      r1,[r5,#4]
550 
551 	mov      r1,#0x50		;WrNFCmd(READCMD2)
552 	strb     r1,[r5,#8]
553 	mov      r1, #5;6		;6->5
554 	strb     r1,[r5,#0xc]	;WrNFAddr(5);(6) 6->5
555 	strb     r0,[r5,#0xc]	;WrNFAddr(addr)
556 	mov      r1,r0,lsr #8	;WrNFAddr(addr>>8)
557 	strb     r1,[r5,#0xc]
558 	cmp      r6,#0			;if(NandAddr)		
559 	movne    r0,r0,lsr #16	;WrNFAddr(addr>>16)
560 	strneb   r0,[r5,#0xc]
561 	
562 ;	bl		WaitNandBusy	;WaitNFBusy()
563 	;do not use WaitNandBusy, after WaitNandBusy will read part A!
564 	mov	r0, #100
565 1
566 	subs	r0, r0, #1
567 	bne	%B1
568 2
569 	ldr	r0, [r5, #0x20]
570 	tst	r0, #1
571 	beq	%B2	
572 
573 	ldrb	r0, [r5,#0x10]	;RdNFDat()
574 	sub		r0, r0, #0xff
575 	
576 	mov      r1,#0			;WrNFCmd(READCMD0)
577 	strb     r1,[r5,#8]
578 	
579 	ldr      r1,[r5,#4]		;NFChipDs()
580 	orr      r1,r1,#2
581 	str      r1,[r5,#4]
582 	
583 	mov		pc, r7
584 	

543-549行保存LR,用于返回函数。然后是使能nandflash,和上面几个函数是一样的。第551-560行发送命令0x50,是READ2命令,其实是想读OOB的信息,碰到的micro,sandisk,ToshibaNANDFLASH,查找坏块的方法就是读出OOB中的一个字节,然后与0xff相比,这里也是一样,发送完命令后,下面是地址。

565-571BUSY信号的等待。然后测试现在是BUSY,还是READY信号。

573-583行是取出读出的数据与0xff比较,返回函数。   

585 ReadNandPage
586 	mov		 r7,lr
587 	mov      r4,r1
588 	mov      r5,#NFCONF
589 
590 	ldr      r1,[r5,#4]		;NFChipEn()
591 	bic      r1,r1,#2
592 	str      r1,[r5,#4]	
593 
594 	mov      r1,#0			;WrNFCmd(READCMD0)
595 	strb     r1,[r5,#8]	
596 	strb     r1,[r5,#0xc]	;WrNFAddr(0)
597 	strb     r0,[r5,#0xc]	;WrNFAddr(addr)
598 	mov      r1,r0,lsr #8	;WrNFAddr(addr>>8)
599 	strb     r1,[r5,#0xc]	
600 	cmp      r6,#0			;if(NandAddr)		
601 	movne    r0,r0,lsr #16	;WrNFAddr(addr>>16)
602 	strneb   r0,[r5,#0xc]
603 	
604 	ldr      r0,[r5,#4]		;InitEcc() 
605 	orr      r0,r0,#0x10
606 	str      r0,[r5,#4]
607 	
608 	bl       WaitNandBusy	;WaitNFBusy()
609 	
610 	mov      r0,#0			;for(i=0; i<512; i++)
611 1
612 	ldrb     r1,[r5,#0x10]	;buf[i] = RdNFDat()
613 	strb     r1,[r4,r0]
614 	add      r0,r0,#1
615 	bic      r0,r0,#0x10000
616 	cmp      r0,#0x200
617 	bcc      %B1
618 	
619 	ldr      r0,[r5,#4]		;NFChipDs()
620 	orr      r0,r0,#2
621 	str      r0,[r5,#4]
622 		
623 	mov		 pc,r7
624 	]
625 

586-592行是NANDFLASH的片选

594-608行发送命令0,然后发送地址0,就是从页内地址0,也就是块的第一个页开始读出。紧跟着下面几个地址命令。(参考K9F1208 PAGE8)看地址应该如何赋值。

608行等待数据到FLASH内部的缓冲区。

610-617行把取出的数据放到要用的BUFFER中,因为页的大小为512个,所以会有512个字节的数据。

619-623disable nandflash,然后返回.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
init/main.c 是 Linux 内核中的一个关键文件,它包含了 Linux 内核的主要初始化代码。该文件定义了内核的启动过程,包括初始化各个子系统、设备驱动和启动用户空间等。 具体分析 init/main.c 的代码需要查看具体版本的 Linux 内核代码。以下是一个简要的分析概述: 1. 初始化函数: - `start_kernel()`:这是 Linux 内核的入口函数,负责初始化内核数据结构、中断、内存管理、定时器等。 - `rest_init()`:该函数初始化系统的初始化任务(init task),并创建第一个用户进程(通常是 `/sbin/init`)。 2. 初始化子系统: - `kernel_init()`:该函数调用各个子系统的初始化函数,如进程管理(`sched_init()`)、内存管理(`mm_init()`)、文件系统(`vfs_caches_init()`)等。 3. 设备驱动初始化: - `drivers/base/init.c` 中的 `driver_init()` 函数:负责注册各个设备驱动。 - `drivers/char/tty_io.c` 中的 `tty_init()` 函数:初始化终端设备驱动。 4. 用户空间启动: - `kernel_init_freeable()`:该函数通过调用 `kernel_init()` 初始化用户空间,并将系统状态切换到用户模式。 总的来说,init/main.c 文件是 Linux 内核启动过程的关键文件,负责完成内核数据结构的初始化、子系统和设备驱动的初始化,最终将系统状态切换到用户空间。它是 Linux 内核启动过程中的重要一环。对于具体的代码细节,需要查看具体版本的 Linux 内核代码

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值