Nandflash 驱动移植 (五)

Nandflash驱动移植系列文章导航:

Nandflash 驱动移植 (一)

Nandflash 驱动移植 (二)

Nandflash 驱动移植 (三)

Nandflash 驱动移植 (四)

Nandflash 驱动移植 (五)

Nandflash 驱动移植 (六)

一共六篇

 

 

 

接着上一篇

 

1、ECC_CorrectData()   查找ECC错误并矫正

BOOL ECC_CorrectData(SECTOR_ADDR sectoraddr, LPBYTE pData, UINT32 nRetEcc, ECC_CORRECT_TYPE nType)
{
	DWORD  nErrDataNo;
	DWORD  nErrBitNo;
	//BYTE Status;
	BYTE nErrDataNum;
	UINT8  nErrByteNum;
	UINT8 countdown = 155;
	BOOL bRet = TRUE;

	//RETAILMSG(1, (TEXT("#### FMD_DRIVER:::ECC_CorrectData %x, %x, %x\n"), sectoraddr, nRetEcc, nType));

#if 0
	if( (nRetEcc & NF_ECC8ERR0_ECC_READY) )
		return TRUE;
#endif
	// 8bit ECC error searching engine needs mini mum 372 cycles to find any error
	countdown = 372;
	while(countdown--);
	// 等待ECC错误查找完毕
	while(NF_ECC8_ERR0 & 0x80000000);
	// 获取8bit ECC解码结果
	nErrDataNum = NF_ECC8BIT_NUM;

	// No error, if free page (all 0xff)
	if( (g_pNFConReg->NF8ECCERR0 >> 29) & 0x1 ){
		nErrDataNum = 0;
	}

	if (nErrDataNum == 0)
	{
		bRet = TRUE;
		RETAILMSG(0,(TEXT("No Error\n")));
		goto finished;
	}
	else if (nErrDataNum == 9)
	{
		bRet = FALSE;
		RETAILMSG(1,(TEXT("More than 8-bit error, uncorrectable\n")));
		goto finished;
	}
	else if (nErrDataNum > 9)
	{
		bRet = FALSE;
		RETAILMSG(1,(TEXT("Reserved\n")));
		goto finished;
	}
	else
	{
		// 获取错误位对应的位置
		for (nErrByteNum = 1; nErrByteNum <= nErrDataNum; nErrByteNum++)
		{
			switch(nErrByteNum)
			{
			case 1:		nErrDataNo = NF_ECC8LOCATION_BYTE1;
				break;
			case 2:		nErrDataNo = NF_ECC8LOCATION_BYTE2;
				break;
			case 3:		nErrDataNo = NF_ECC8LOCATION_BYTE3;
				break;
			case 4:		nErrDataNo = NF_ECC8LOCATION_BYTE4;
				break;
			case 5:		nErrDataNo = NF_ECC8LOCATION_BYTE5;
				break;
			case 6:		nErrDataNo = NF_ECC8LOCATION_BYTE6;
				break;
			case 7:		nErrDataNo = NF_ECC8LOCATION_BYTE7;
				break;
			case 8:		nErrDataNo = NF_ECC8LOCATION_BYTE8;
				break;
			default:break;
			}
			// 定位到具体错误位的位置
			nErrBitNo = NF_ECC8LOCATION_BIT(nErrByteNum);
			// 矫正错误位
			(pData)[nErrDataNo] ^= (1<<nErrBitNo);   
			RETAILMSG(1, (TEXT("8bit ECC_CorrectData %x, %x, %x, %x\n"), nErrDataNum, nErrByteNum, nErrDataNo, nErrBitNo));
		}
	}

finished:
    return bRet;
}

这里是修改后的,支持8bit ECC校验。

注意到上面的
  // No error, if free page (all 0xff)
  if( (g_pNFConReg->NF8ECCERR0 >> 29) & 0x1 ){
     nErrDataNum = 0;
  }

代码了吗?让我们对比手册看看这个NF8ECCERR0[29]是何许人也

看到了吧,NF8ECCERR0[29]是保留位。然而,我参考6410的MLC的BSP源码,发现里面有用到这个位来判断是否全为0xff。在飞凌最新发布的linux3.0的源码中也查看到有用到这个保留位,而且还是针对8bit ECC来使用的,参考的两个源码都支持这两个Nandflash。为什么6410的芯片文档上会写成是保留位?是笔误还是有所保留?为啥三星自己的MLC的BSP中也有使用?具体大家自己纠结去吧,反正上面这样使用了也没见着啥不良影响。

 

 

2、FMD_LB_ReadSector()

原来的代码:

BOOL FMD_LB_ReadSector(SECTOR_ADDR startSectorAddr, LPBYTE pSectorBuff, PSectorInfo pSectorInfoBuff, DWORD dwNumSectors)
{
    ULONG SectorAddr = (ULONG)startSectorAddr;
    DWORD       i, j;
    volatile DWORD        rddata;
    UINT32 nRetEcc = 0;
    DWORD MECCBuf[16],tempMECCBuf[2];  // gjl 8
    UINT16 nSectorLoop,nSectorLoop1;
    int NewSpareAddr = 4096;  //gjl 2048
    int NewDataAddr = 0;
    int NewSectorAddr = startSectorAddr;
    int SectorSpareAddr;
    UINT8 TempSectorInfo[40];
	BYTE *pSectorBuff1 = (BYTE *)pSectorBuff;
	UINT16 k=40;
#if CHECK_SPAREECC
    DWORD SECCBuf[4];   // gjl 2
#endif

#if (NAND_DEBUG)
    RETAILMSG(1,(TEXT("#### FMD_DRIVER:::FMD_LB_READSECTOR %x %x\n"),startSectorAddr,NewDataAddr));
#endif

    if (!pSectorBuff && !pSectorInfoBuff)
    {
        return(FALSE);
    }


	    if ( dwNumSectors > 1 )
    {
        RETAILMSG(1, (TEXT("######## FATAL ERROR => FMD::FMD_ReadSector->dwNumsectors is bigger than 1. \n")));
        return FALSE;
    }


	
	if (!pSectorBuff)
    {
        if (!NAND_LB_ReadSectorInfo(startSectorAddr, pSectorInfoBuff))
        {
			#if (NAND_DEBUG)
			    RETAILMSG(1,(TEXT("#### FMD_DRIVER:::54321\n")));
			#endif
	
            return FALSE;
        }
		#if (NAND_DEBUG)
			RETAILMSG(1,(TEXT("#### FMD_DRIVER:::12345\n")));
		#endif
	
        return TRUE;
    }


	NF_nFCE_L();

    NF_CLEAR_RB();

    NF_CMD(CMD_READ);                            // Send read command.

    NF_ADDR((NewSpareAddr)&0xff);
    NF_ADDR((NewSpareAddr>>8)&0xff);
    NF_ADDR((NewSectorAddr) & 0xff);
    NF_ADDR((NewSectorAddr >> 8) & 0xff);
#if    LB_NEED_EXT_ADDR
    NF_ADDR((NewSectorAddr >> 16) & 0xff);
#endif

    NF_CMD(CMD_READ3);                        // 2nd command

    NF_DETECT_RB();                                // Wait for command to complete.

    NF_MSGLENGTH_512();
    NF_ECCTYPE_4BIT();
	
    if (pSectorInfoBuff)
    {

        pSectorInfoBuff->bBadBlock = NF_RDDATA_BYTE();
        pSectorInfoBuff->dwReserved1 = NF_RDDATA_WORD();
        pSectorInfoBuff->bOEMReserved = NF_RDDATA_BYTE();

        pSectorInfoBuff->wReserved2 = NF_RDDATA_BYTE();
        pSectorInfoBuff->wReserved2 |= (NF_RDDATA_BYTE()<<8);
    }
    else
    {
         for(i=0; i<sizeof(SectorInfo)/sizeof(DWORD); i++)
         {
            rddata = (DWORD) NF_RDDATA_WORD();        // read and trash the data
         }
    }

    for (nSectorLoop = 0; nSectorLoop < SECTORS_PER_PAGE*2; nSectorLoop++)
    {
        MECCBuf[nSectorLoop] = NF_RDDATA_WORD();
    }

#if DEBUG_WRITE_READ_EQUAL
    for (nSectorLoop = 0; nSectorLoop < 8; nSectorLoop++)
    {
	    g_MECCBuf_R[nSectorLoop] = MECCBuf[nSectorLoop];
    }
#endif


	
    for (nSectorLoop = 0; nSectorLoop < SECTORS_PER_PAGE; nSectorLoop++)
    {
        NewDataAddr = nSectorLoop * SECTOR_SIZE;

        NF_CMD(CMD_RDO);                            // Send read command.
        NF_ADDR((NewDataAddr)&0xff);
        NF_ADDR((NewDataAddr>>8)&0xff);
        NF_CMD(CMD_RDO2);    // 2nd command


		NF_MSGLENGTH_512();
		NF_ECCTYPE_4BIT();

        NF_RSTECC();
        NF_MECC_UnLock();

        if( ((DWORD) (pSectorBuff+nSectorLoop*SECTOR_SIZE)) & 0x3)
        {
            for(i=0; i<SECTOR_SIZE/sizeof(DWORD); i++)
            {
                rddata = (DWORD) NF_RDDATA_WORD();
                (pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+0] = (BYTE)(rddata & 0xff);
                (pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+1] = (BYTE)(rddata>>8 & 0xff);
                (pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+2] = (BYTE)(rddata>>16 & 0xff);
                (pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+3] = (BYTE)(rddata>>24 & 0xff);
            }
        }
        else
        {
            RdPage512(pSectorBuff+nSectorLoop*SECTOR_SIZE);                    // Read page/sector data.
        }

        SectorSpareAddr = NewSpareAddr+8+nSectorLoop*8;
		NF_WRDATA_WORD(MECCBuf[2*nSectorLoop]);
		SectorSpareAddr = NewSpareAddr+8+nSectorLoop*8+4;
		NF_WRDATA_WORD(MECCBuf[2*nSectorLoop+1]);
        NF_MECC_Lock();

        //decode done
        while (!(NF_RDSTAT & (1<<6)));
        tempMECCBuf[0]= NF_RDMECC0();
        tempMECCBuf[1] = NF_RDMECC1();

		pSectorBuff1 = pSectorBuff+nSectorLoop*SECTOR_SIZE;

        if (!ECC_CorrectData(startSectorAddr, pSectorBuff1, nRetEcc, ECC_CORRECT_MAIN))
        {
			RETAILMSG(1,(TEXT("ECC ERROR\n")));
            return FALSE;
        }
    }

    NF_nFCE_H();


	
	return TRUE;
}

这个是飞凌BSP中的源码,里面需要修改的地方还是挺多的。

首先,来看定义部分的
DWORD MECCBuf[16],tempMECCBuf[2];  // gjl 8

由于将要用的是8bit的ECC校验,这个ECC的buffer就应该是32,1page=8*512byte,每读取512byte数据产生的ECC存放在4个32位的寄存器中,所以需要8*4个buffer:

DWORD MECCBuf[32];

至于tempMECCBuf,从上述代码中就可以看出就一垃圾,根本没用到,这里就把它删了。

定义完之后,我们需要使能一些相关的中断(不这样搞的话,发现无法正常校验ECC,具体原因请知道的朋友告知一声)

if (!pSectorBuff && !pSectorInfoBuff)
{
        return(FALSE);
}

的后面,我们添加以下代码:

	g_pNFConReg->NFCONT |= (1<<10);	// Enable illegal access interrupt control
	g_pNFConReg->NFCONT |= (1<<9);	// Enable RnB interrupt
	g_pNFConReg->NFCONT |= (1<<12);	// Enable 4bit,8bit ECC decoding completion interrupt control

 

接下来看到代码:(中间省略的那部分就不介绍了,大家有空可以参考一下LoongEmbedded的csdn blog)

NF_MSGLENGTH_512();

NF_ECCTYPE_4BIT();

我们使用8bit ECC,所以把 NF_ECCTYPE_4BIT(); 修改成

	NF_ECCTYPE_8BIT();

之后就是读取SectorInfo数据的操作,再过来就是读取ECC数据的操作:
    for (nSectorLoop = 0; nSectorLoop < SECTORS_PER_PAGE*2; nSectorLoop++)
    {
        MECCBuf[nSectorLoop] = NF_RDDATA_WORD();
    }

由于,我们使用的是8bit ECC,上面只读取了8*2 * 4字节的ECC,而8bit的ECC需要8*4 *4字节,所以修改成:

    for (nSectorLoop = 0; nSectorLoop < SECTORS_PER_PAGE*4; nSectorLoop++)	// 8bit ECC,4096/page = 8*512, it has 8*4(register) ECC data
    {
        MECCBuf[nSectorLoop] = NF_RDDATA_WORD();
    }



接下来直接看到循环读取一页数据的操作:

就是
    for (nSectorLoop = 0; nSectorLoop < SECTORS_PER_PAGE; nSectorLoop++)
    {
        NewDataAddr = nSectorLoop * SECTOR_SIZE;

        NF_CMD(CMD_RDO);                            // Send read command.
        NF_ADDR((NewDataAddr)&0xff);
        NF_ADDR((NewDataAddr>>8)&0xff);
        NF_CMD(CMD_RDO2);    // 2nd command


  NF_MSGLENGTH_512();
  NF_ECCTYPE_4BIT();

这里,把上面的 NF_ECCTYPE_4BIT(); 修改成:

	NF_ECCTYPE_8BIT();

顺便在上面这一句后面加上两句:

	NF_ECC_8BIT_STOP();	// init 8bit ECC decoding
	NF_ECC_DIRECTION_IN();	// 4/8BIT ECC Decoding, read page

接下来,原代码是:

        NF_RSTECC();
        NF_MECC_UnLock();

我这里把这两个操作的顺序换一下,变成:

        NF_MECC_UnLock();
        NF_RSTECC();


在NF_RSTECC()之前必须设置 NF_ECC_8BIT_STOP(); ,因为文档中有说到: if you want to stop current work and start encoding/decoding for new data, you must set 8bitStop(NFCONT[11]) before set InitMECC(NFCONT[5]) bit.

接下来的代码就是读取512字节数据的:
        if( ((DWORD) (pSectorBuff+nSectorLoop*SECTOR_SIZE)) & 0x3)
        {
            for(i=0; i<SECTOR_SIZE/sizeof(DWORD); i++)
            {
                rddata = (DWORD) NF_RDDATA_WORD();
                (pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+0] = (BYTE)(rddata & 0xff);
                (pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+1] = (BYTE)(rddata>>8 & 0xff);
                (pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+2] = (BYTE)(rddata>>16 & 0xff);
                (pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+3] = (BYTE)(rddata>>24 & 0xff);
            }
        }
        else
        {
            RdPage512(pSectorBuff+nSectorLoop*SECTOR_SIZE);                    // Read page/sector data.
        }

然后,就看到代码把前面读取到的ECC接着写进去了,这里应该是写进去的ECC与读取产生的ECC在ECC模块中进行对比,用于查找错误位
        SectorSpareAddr = NewSpareAddr+8+nSectorLoop*8;
  NF_WRDATA_WORD(MECCBuf[2*nSectorLoop]);
  SectorSpareAddr = NewSpareAddr+8+nSectorLoop*8+4;
  NF_WRDATA_WORD(MECCBuf[2*nSectorLoop+1]);
        NF_MECC_Lock();

这里,同样需要修改成写入8bit ECC的:

		SectorSpareAddr = NewSpareAddr+8+nSectorLoop*8;
		NF_WRDATA_WORD(MECCBuf[4*nSectorLoop]);
		SectorSpareAddr = NewSpareAddr+8+nSectorLoop*8+4;
		NF_WRDATA_WORD(MECCBuf[4*nSectorLoop+1]);
		SectorSpareAddr = NewSpareAddr+8+nSectorLoop*8+8;
		NF_WRDATA_WORD(MECCBuf[4*nSectorLoop+2]);
		SectorSpareAddr = NewSpareAddr+8+nSectorLoop*8+12;
		NF_WRDATA_WORD(MECCBuf[4*nSectorLoop+3]);
		NF_MECC_Lock();


之后,就是等待解码操作的完成:
        //decode done
        while (!(NF_RDSTAT & (1<<6)));
        tempMECCBuf[0]= NF_RDMECC0();
        tempMECCBuf[1] = NF_RDMECC1();

后面这两句含有tempMECCBuf的操作可以直接删除了,没用的。处理完这个之后,紧接着,就是查找ECC错误并进行矫正了:


  pSectorBuff1 = pSectorBuff+nSectorLoop*SECTOR_SIZE;

        if (!ECC_CorrectData(startSectorAddr, pSectorBuff1, nRetEcc, ECC_CORRECT_MAIN))
        {
   RETAILMSG(1,(TEXT("ECC ERROR\n")));
            return FALSE;
        }
    }

 

    NF_nFCE_H();

在 NF_nFCE_H(); 这句之前,我们需要把使能的一些中断关闭了:

	g_pNFConReg->NFCONT &= ~(1<<10);	// Disable illegal access interrupt control
	g_pNFConReg->NFCONT &= ~(1<<9);		// Disable RnB interrupt


 

 

 

3、NAND_LB_ReadSectorInfo()

原BSP代码:

BOOL NAND_LB_ReadSectorInfo(SECTOR_ADDR sectorAddr, PSectorInfo pInfo)
{
    BOOL bRet = TRUE;
    int NewSpareAddr = 4096;  //gjl 2048
    int NewSectorAddr = sectorAddr;
    DWORD MECCBuf[16];  // gjl 8
    UINT16 nSectorLoop, i;
    UINT8 TempInfo[40];
#if CHECK_SPAREECC
    DWORD SECCBuf[4];   //gjl 2
    UINT32 nRetEcc = 0;
#endif

    NF_nFCE_L();

    NF_CLEAR_RB();

    NF_CMD(CMD_READ);                            // Send read confirm command.

    NF_ADDR((NewSpareAddr)&0xff);
    NF_ADDR((NewSpareAddr>>8)&0xff);
    NF_ADDR((NewSectorAddr)&0xff);
    NF_ADDR((NewSectorAddr>>8) & 0xff);
#if    LB_NEED_EXT_ADDR
    NF_ADDR((NewSectorAddr >> 16) & 0xff);
#endif

    NF_CMD(CMD_READ3);

    NF_DETECT_RB();


    pInfo->bBadBlock = NF_RDDATA_BYTE();
    pInfo->dwReserved1  = NF_RDDATA_WORD();
    pInfo->bOEMReserved = NF_RDDATA_BYTE();
	
    pInfo->wReserved2 = NF_RDDATA_BYTE();
    pInfo->wReserved2 |= (NF_RDDATA_BYTE()<<8);

    for (nSectorLoop = 0; nSectorLoop < SECTORS_PER_PAGE*2; nSectorLoop++)
    {
        MECCBuf[nSectorLoop] = NF_RDDATA_WORD();
    }

    NF_nFCE_H();
 #if (NAND_DEBUG)
    RETAILMSG(1,(TEXT("#### FMD_DRIVER:::56565656\n")));
#endif
	
    return bRet;
}

还是先看定义的 DWORD MECCBuf[16];  // gjl 8

这个我们要改成:

    DWORD MECCBuf[32];

 

接着在 NF_nFCE_L(); 操作之前,添加:

	NF_ECCTYPE_8BIT();		// use 8bit ECC type
	NF_ECC_8BIT_STOP();	// init 8bit ECC decoding

 

然后,又看到读取ECC的操作:
    for (nSectorLoop = 0; nSectorLoop < SECTORS_PER_PAGE*2; nSectorLoop++)
    {
        MECCBuf[nSectorLoop] = NF_RDDATA_WORD();
    }

这个,我们需要改成:

    for (nSectorLoop = 0; nSectorLoop < SECTORS_PER_PAGE*4; nSectorLoop++)
    {
        MECCBuf[nSectorLoop] = NF_RDDATA_WORD();
    }



 

 

4、FMD_SB_ReadSector()

BOOL FMD_SB_ReadSector(SECTOR_ADDR startSectorAddr, LPBYTE pSectorBuff, PSectorInfo pSectorInfoBuff, DWORD dwNumSectors)
{
    ULONG SectorAddr = (ULONG)startSectorAddr;
    ULONG MECC;
    UINT32 nRet = TRUE;
    UINT32 nRetEcc = 0;

#if (NAND_DEBUG)
    RETAILMSG(1,(TEXT("#### FMD_DRIVER:::FMD_sbreadT \n")));
#endif

    if (!pSectorBuff && !pSectorInfoBuff)
    {
        RETAILMSG(1,(TEXT("[FMD:ERR] FMD_SB_ReadSector(0x%08x, 0x%08x) : Invalid Parameter\n"), pSectorBuff, pSectorInfoBuff));
        return(FALSE);
    }

    while (dwNumSectors--)
    {
        NF_RSTECC();
        NF_MECC_UnLock();
        NF_nFCE_L();

        if (!pSectorBuff)
        {
            NF_CLEAR_RB();
            NF_CMD(CMD_READ2);            // Send read confirm command.

            NF_ADDR(0);                                    // Ignored.
            NF_ADDR(SectorAddr         & 0xff);            // Page address.
            NF_ADDR((SectorAddr >>  8) & 0xff);
#if    SB_NEED_EXT_ADDR
            NF_ADDR((SectorAddr >> 16) & 0xff);
#endif

            NF_DETECT_RB();

            RdPageInfo((PBYTE)pSectorInfoBuff);    // Read page/sector information.

            pSectorInfoBuff++;
        }
        else
        {
            NF_CLEAR_RB();

            NF_CMD(CMD_READ);                    // Send read command.

            NF_ADDR(0);                                    // Column = 0.
            NF_ADDR(SectorAddr         & 0xff);            // Page address.
            NF_ADDR((SectorAddr >>  8) & 0xff);
#if    SB_NEED_EXT_ADDR
            NF_ADDR((SectorAddr >> 16) & 0xff);
#endif

            NF_DETECT_RB();                    // Wait for command to complete.

            if( ((DWORD) pSectorBuff) & 0x3)
            {
                RdPage512Unalign (pSectorBuff);
            }
            else
            {
                RdPage512(pSectorBuff);                    // Read page/sector data.
            }

            NF_MECC_Lock();

            if (pSectorInfoBuff)
            {
                RdPageInfo((PBYTE)pSectorInfoBuff);        // Read page/sector information.
                pSectorInfoBuff ++;
            }
            else
            {
                BYTE TempInfo[8];
                RdPageInfo(TempInfo);                       // Read page/sector information.
            }

            MECC  = NF_RDDATA_BYTE() << 0;
            MECC |= NF_RDDATA_BYTE() << 8;
            MECC |= NF_RDDATA_BYTE() << 16;
            MECC |= (NF_RDMECC0() &0xff000000);
            //MECC |= NF_RDDATA_BYTE() << 24;

            NF_WRMECCD0( ((MECC&0xff00)<<8)|(MECC&0xff) );
             NF_WRMECCD1( ((MECC&0xff000000)>>8)|((MECC&0xff0000)>>16) );

             nRetEcc = NF_ECC_ERR0;

            switch(nRetEcc & 0x3)
            {
                case 0:    // No Error
                    nRet = TRUE;
                    break;
                case 1:    // 1-bit Error(Correctable)
                    RETAILMSG(1,(TEXT("ECC correctable error(0x%x)\n"), SectorAddr));
                    (pSectorBuff)[(nRetEcc>>7)&0x7ff] ^= (1<<((nRetEcc>>4)&0x7));
                    nRet = TRUE;
                    break;
                case 2:    // Multiple Error
                    RETAILMSG(1,(TEXT("ECC Uncorrectable error(0x%x)\n"), SectorAddr));
                    nRet = FALSE;
                    break;
                case 3:    // ECC area Error
                    RETAILMSG(1,(TEXT("ECC area error\n")));
                default:
                    nRet = FALSE;
                    break;
            }
            pSectorBuff += NAND_SECTOR_SIZE;
        }
        NF_nFCE_H();
        ++SectorAddr;
    }

    return(nRet);
}

FMD_SB_ReadSector()是介绍SLC读取操作的,这里不用修改。
 

 

到此,这一篇就把ECC矫正和读取数据的部分给搞掂了。下一篇将介绍写数据的部分

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值