fmd.cpp

来自「SAMSUNG S3C6410 CPU BSP for winmobile6」· C++ 代码 · 共 2,345 行 · 第 1/4 页

CPP
2,345
字号
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft end-user
// license agreement (EULA) under which you licensed this SOFTWARE PRODUCT.
// If you did not accept the terms of the EULA, you are not authorized to use
// this source code. For a copy of the EULA, please see the LICENSE.RTF on your
// install media.
//
#include <bsp_cfg.h>
#include <fmd.h>
#include <s3c6410_nand.h>
#include <s3c6410_syscon.h>
#include <bsp_args.h>
#include <ethdbg.h>
#include "Cfnand.h"
#if MAGNETO
#include <image_cfg.h>
#endif

#define CHECK_SPAREECC	(1)
#define NAND_DEBUG		(0)
#define USE_LOCK_TIGHT	(1)			// CE does not use lock tight
#define NAND_BASE		(0xB0200000)	// PA:0x70200000
#define IOPORT_BASE		(0xB2B08000)	// PA:0x7F008000
#define SYSCON_BASE		(0xB2A0F000)	// PA:0x7E00F000

#if MAGNETO
#define MAX_REGIONS 16
#endif

BOOL NEED_EXT_ADDR = TRUE;

static volatile S3C6410_NAND_REG *s6410NAND = (S3C6410_NAND_REG *)NAND_BASE;
static volatile S3C6410_SYSCON_REG *s6410SYSCON = (S3C6410_SYSCON_REG *)SYSCON_BASE;

#if MAGNETO
BSP_ARGS *pBSPArgs;
#endif

extern "C"
{
	void RdPage512(unsigned char *bufPt);
	void RdPage512Unalign(unsigned char *bufPt);
	void WrPage512(unsigned char *bufPt);
	void WrPage512Unalign(unsigned char *bufPt);
	void WrPageInfo(PBYTE pBuff);
	void RdPageInfo(PBYTE pBuff);
}

NANDDeviceInfo GetNandInfo(void) { return stDeviceInfo; }

#if MAGNETO
static BOOL DefineLayout();

static FlashRegion g_pRegionTable[MAX_REGIONS];

static DWORD g_dwNumRegions;
static FlashInfo g_flashInfo;

// TODO: Make sector size generic
static BYTE g_pFLSBuffer[NAND_LB_PAGE_SIZE];

#define DEFAULT_COMPACTION_BLOCKS 4
#endif


BOOL ECC_CorrectData(SECTOR_ADDR sectoraddr, LPBYTE pData, UINT32 nRetEcc, ECC_CORRECT_TYPE nType)
{
	DWORD  nErrStatus;
	DWORD  nErrDataNo;
	DWORD  nErrBitNo;
	UINT32 nErrDataMask;
	UINT32 nErrBitMask = 0x7;
	BOOL bRet = TRUE;

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

	if (nType == ECC_CORRECT_MAIN)
	{
		nErrStatus   = 0;
		nErrDataNo   = 7;
		nErrBitNo    = 4;
		nErrDataMask = 0x7ff;
	}
	else if (nType == ECC_CORRECT_SPARE)
	{
		nErrStatus   = 2;
		nErrDataNo   = 21;
		nErrBitNo    = 18;
		nErrDataMask = 0xf;
	}
	else
	{
		return FALSE;
	}

	switch((nRetEcc>>nErrStatus) & 0x3)
	{
		case 0:	// No Error
			bRet = TRUE;
			break;
		case 1:	// 1-bit Error(Correctable)
			RETAILMSG(1,(TEXT("%cECC correctable error(0x%x). Byte:%d, bit:%d\r\n"), ((nType==ECC_CORRECT_MAIN)?'M':'S'), sectoraddr, (nRetEcc>>nErrDataNo)&nErrDataMask, (nRetEcc>>nErrBitNo)&nErrBitMask));
			(pData)[(nRetEcc>>nErrDataNo)&nErrDataMask] ^= (1<<((nRetEcc>>nErrBitNo)&nErrBitMask));
			bRet = TRUE;
			break;
		case 2:	// Multiple Error
			RETAILMSG(1,(TEXT("%cECC Uncorrectable error(0x%x)\r\n"), ((nType==ECC_CORRECT_MAIN)?'M':'S'), sectoraddr));
			bRet = FALSE;
			break;
		case 3:	// ECC area Error
			RETAILMSG(1,(TEXT("%cECC area error\r\n"), ((nType==ECC_CORRECT_MAIN)?'M':'S')));
		default:
			bRet = FALSE;
			break;
	}

	return bRet;
}

/*
	@func   DWORD | ReadFlashID | Reads the flash manufacturer and device codes.
	@rdesc  Manufacturer and device codes.
	@comm	
	@xref   
*/
static DWORD ReadFlashID(void)
{
	BYTE Mfg, Dev;
	int i;

	NF_nFCE_L();						// Deselect the flash chip.
	NF_CMD(CMD_READID);					// Send flash ID read command.

	NF_ADDR(0);

	for (i=0; i<10; i++)
	{
		Mfg	= NF_RDDATA_BYTE();
		if (Mfg == 0xEC || Mfg == 0x98) break;
	}

	Dev	= NF_RDDATA_BYTE();
	
	NF_nFCE_H();
	
	return ((DWORD)(Mfg<<8)+Dev);
}

/*
	@func   PVOID | FMD_Init | Initializes the Smart Media NAND flash controller.
	@rdesc  Pointer to S3C6410 NAND controller registers.
	@comm	
	@xref   
*/
PVOID FMD_Init(LPCTSTR lpActiveReg, PPCI_REG_INFO pRegIn, PPCI_REG_INFO pRegOut)
{
	// Caller should have specified NAND controller address.
	//
	BOOL bNandExt = FALSE;	
	UINT8 nMID, nDID;	
	int nCnt;
	BOOL bLastMode = SetKMode(TRUE);
	volatile DWORD rdid;

	RETAILMSG(1,(TEXT("[FMD] ++FMD_Init()\r\n")));

#if MAGNETO
	pBSPArgs = ((BSP_ARGS *) IMAGE_SHARE_ARGS_UA_START);
#endif

	if (pRegIn && pRegIn->MemBase.Num && pRegIn->MemBase.Reg[0])
		s6410NAND = (S3C6410_NAND_REG *)(pRegIn->MemBase.Reg[0]);
	else
		s6410NAND = (S3C6410_NAND_REG *)NAND_BASE;


	// Configure SMC Chip Select Mux for NAND
	//
	s6410SYSCON = (S3C6410_SYSCON_REG *)SYSCON_BASE;
	s6410SYSCON->MEM_SYS_CFG = (s6410SYSCON->MEM_SYS_CFG & ~(0x1000)); // 8-bit data width

	s6410SYSCON->MEM_SYS_CFG = (s6410SYSCON->MEM_SYS_CFG & ~(0x3F)) | (0x00); // Xm0CSn[2] = NFCON CS0

	

	// Set up initial flash controller configuration.
	//
	s6410NAND->NFCONF = (NAND_TACLS  <<  12) | (NAND_TWRPH0 <<  8) | (NAND_TWRPH1 <<  4);
	s6410NAND->NFCONT = (0<<17)|(0<<16)|(0<<10)|(0<<9)|(0<<8)|(1<<7)|(1<<6)|(1<<5)|(1<<4)|(0x3<<1)|(1<<0);
	s6410NAND->NFSTAT = (1<<4);

	// Get manufacturer and device codes.
	rdid = ReadFlashID();
	RETAILMSG(1, (TEXT("[FMD:INF] FMD_Init() : Read ID = 0x%x\n\r"), rdid));


	nMID = (UINT8)(rdid >> 8);
	nDID = (UINT8)(rdid & 0xff);

	RETAILMSG(1, (TEXT("[FMD:INF] FMD_Init() : Read ID = 0x%08x\n\r"), rdid));

	for (nCnt = 0; astNandSpec[nCnt].nMID != 0; nCnt++)
	{
		if (nDID == astNandSpec[nCnt].nDID)
		{
			bNandExt = TRUE;
			break;
		}
	}

	if (!bNandExt)
	{
		RETAILMSG(1, (TEXT("[FMD:ERR] FMD_Init() : Unknown ID = 0x%08x\n\r"), rdid));
		return NULL;
	}

	NUM_OF_BLOCKS = astNandSpec[nCnt].nNumOfBlks;
	SECTORS_PER_PAGE = astNandSpec[nCnt].nSctsPerPg;
	PAGES_PER_BLOCK = (IS_LB)?astNandSpec[nCnt].nPgsPerBlk:NAND_PAGE_CNT;
	
	RETAILMSG(1,(TEXT("SECTORS_PER_PAGE=%d\n"),SECTORS_PER_PAGE));


#if MAGNETO
	DefineLayout();
#endif

	SetKMode (bLastMode);

	return((PVOID)s6410NAND);
}


/*
	@func   BOOL | FMD_ReadSector | Reads the specified sector(s) from NAND flash.
	@rdesc  TRUE = Success, FALSE = Failure.
	@comm	
	@xref   
*/
BOOL FMD_ReadSector(SECTOR_ADDR startSectorAddr, LPBYTE pSectorBuff, PSectorInfo pSectorInfoBuff, DWORD dwNumSectors)
{
	BOOL bRet;

#if (NAND_DEBUG)
	RETAILMSG(1, (TEXT("FMD::FMD_ReadSector 0x%x \r\n"), startSectorAddr));
#endif

	if ( startSectorAddr < (unsigned)wPRIMARY_NAND_BLOCKS*PAGES_PER_BLOCK )
	{
		if ( astNandSpec[dwPrimaryNandDevice].nSctsPerPg == 4 )
			bRet = FMD_LB_ReadSector(startSectorAddr, pSectorBuff, pSectorInfoBuff, dwNumSectors, USE_NFCE);
		else
			bRet = FMD_SB_ReadSector(startSectorAddr, pSectorBuff, pSectorInfoBuff, dwNumSectors, USE_NFCE);
	}
	else
	{
		if ( astNandSpec[dwSecondaryNandDevice].nSctsPerPg == 4 )
			bRet = FMD_LB_ReadSector(startSectorAddr-wPRIMARY_NAND_BLOCKS*PAGES_PER_BLOCK, pSectorBuff, pSectorInfoBuff, dwNumSectors, USE_GPIO);
		else
			bRet = FMD_SB_ReadSector(startSectorAddr-wPRIMARY_NAND_BLOCKS*PAGES_PER_BLOCK, pSectorBuff, pSectorInfoBuff, dwNumSectors, USE_GPIO);
	}

	//RETAILMSG(1, (TEXT("FMD::FMD_ReadSector -- \r\n")));

	return bRet;
}


BOOL FMD_WriteSector(SECTOR_ADDR startSectorAddr, LPBYTE pSectorBuff, PSectorInfo pSectorInfoBuff,
                        DWORD dwNumSectors)
{
	BOOL    bRet = TRUE;

#if (NAND_DEBUG)
	RETAILMSG(1, (TEXT("FMD::FMD_WriteSector 0x%x \r\n"), startSectorAddr));
#endif

	if ( startSectorAddr < (unsigned)wPRIMARY_NAND_BLOCKS*PAGES_PER_BLOCK )
	{
		if ( astNandSpec[dwPrimaryNandDevice].nSctsPerPg == 4 )
			bRet = FMD_LB_WriteSector(startSectorAddr, pSectorBuff, pSectorInfoBuff, dwNumSectors, USE_NFCE);
		else
			bRet = FMD_SB_WriteSector(startSectorAddr, pSectorBuff, pSectorInfoBuff, dwNumSectors, USE_NFCE);
	}
	else		// if ( PRIMARY_NAND == SMALL_BLOCK_NAND )
	{
	if ( astNandSpec[dwSecondaryNandDevice].nSctsPerPg == 4 )
			bRet = FMD_LB_WriteSector(startSectorAddr-wPRIMARY_NAND_BLOCKS*PAGES_PER_BLOCK, pSectorBuff, pSectorInfoBuff, dwNumSectors, USE_GPIO);
		else
			bRet = FMD_SB_WriteSector(startSectorAddr-wPRIMARY_NAND_BLOCKS*PAGES_PER_BLOCK, pSectorBuff, pSectorInfoBuff, dwNumSectors, USE_GPIO);
	}

	return bRet;
}

/*
	@func   BOOL | FMD_EraseBlock | Erases the specified flash block.
	@rdesc  TRUE = Success, FALSE = Failure.
	@comm	
	@xref   
*/
BOOL FMD_EraseBlock(BLOCK_ID blockID)
{
	BOOL    bRet = TRUE;
	int i;

#if (NAND_DEBUG)
	//RETAILMSG(1, (TEXT("FMD::FMD_EraseBlock 0x%x \r\n")));
#endif

	if ( blockID < wPRIMARY_NAND_BLOCKS )
	{
		if ( astNandSpec[dwPrimaryNandDevice].nSctsPerPg == 4 )
		{
			for ( i = 0; i < LB_BLOCK_LOOP; i++ )
			{
				bRet = FMD_LB_EraseBlock(blockID*(LB_BLOCK_LOOP) + i, USE_NFCE);
				if ( bRet == FALSE ) break;
			}
		}
		else
		{
			for ( i = 0; i < SB_BLOCK_LOOP; i++ )
			{
				bRet = FMD_SB_EraseBlock(blockID*(SB_BLOCK_LOOP) + i, USE_NFCE);
				if ( bRet == FALSE ) break;
			}
		}
	}
	else
	{
		if ( astNandSpec[dwSecondaryNandDevice].nSctsPerPg == 4 )
		{
			for ( i = 0; i < LB_BLOCK_LOOP; i++ )
			{
				bRet = FMD_LB_EraseBlock((blockID-wPRIMARY_NAND_BLOCKS)*(LB_BLOCK_LOOP) + i, USE_GPIO);
				if ( bRet == FALSE ) break;
			}
		}
		else
		{
			for ( i = 0; i < SB_BLOCK_LOOP; i++ )
			{
				bRet = FMD_SB_EraseBlock(blockID*(SB_BLOCK_LOOP) + i, USE_GPIO);
				if ( bRet == FALSE ) break;
			}
		}
	}

    return bRet;
}

BOOL FMD_Deinit(PVOID hFMD)
{
	return(TRUE);
}


/*
	@func   BOOL | FMD_GetInfo | Provides information on the NAND flash.
	@rdesc  TRUE = Success, FALSE = Failure.
	@comm	
	@xref   
*/
BOOL FMD_GetInfo(PFlashInfo pFlashInfo)
{
	UINT32  nCnt;
	UINT32 nNandID;
	UINT8 nMID, nDID;

    if (!pFlashInfo)
        return(FALSE);
	BOOL bLastMode = SetKMode(TRUE); // for READFlashID();

    pFlashInfo->flashType = NAND;

	nNandID = ReadFlashID();

	nMID = nNandID >> 8;
	nDID = nNandID & 0xff;

	for (nCnt = 0; astNandSpec[nCnt].nMID != 0; nCnt++)
	{
		if (nDID == astNandSpec[nCnt].nDID)
		{
			break;
		}
	}

	dwPrimaryNandDevice = nCnt;

	wPRIMARY_REAL_NAND_BLOCKS = astNandSpec[dwPrimaryNandDevice].nNumOfBlks;
	wSECONDARY_NAND_BLOCKS = 0;
	wSECONDARY_REAL_NAND_BLOCKS = 0;

	//	RETAILMSG(1, (TEXT("dwPrimaryNandDevice : %d(0x%x) \r\n"), dwPrimaryNandDevice, dwPrimaryNandDevice));
	//	RETAILMSG(1, (TEXT("astNandSpec[dwPrimaryNandDevice].nSctsPerPg: %d(0x%x) \r\n"), astNandSpec[dwPrimaryNandDevice].nSctsPerPg, astNandSpec[dwPrimaryNandDevice].nSctsPerPg));
	//	RETAILMSG(1, (TEXT("wPRIMARY_REAL_NAND_BLOCKS : %d(0x%x) \r\n"), wPRIMARY_REAL_NAND_BLOCKS, wPRIMARY_REAL_NAND_BLOCKS));

	if ( astNandSpec[dwPrimaryNandDevice].nSctsPerPg == 4 )	// Primary NAND is Large Block...
	{
		wPRIMARY_NAND_BLOCKS = wPRIMARY_REAL_NAND_BLOCKS;
	}
	else
	{
		wPRIMARY_NAND_BLOCKS = wPRIMARY_REAL_NAND_BLOCKS / 8;
	}

	wNUM_BLOCKS = wPRIMARY_NAND_BLOCKS + wSECONDARY_NAND_BLOCKS;

	//  OK, instead of reading it from the chip, we use the hardcoded
	//  numbers here.

	pFlashInfo->dwNumBlocks = wNUM_BLOCKS;
	pFlashInfo->wSectorsPerBlock = PAGES_PER_BLOCK;
	pFlashInfo->wDataBytesPerSector = NAND_PAGE_SIZE * astNandSpec[dwPrimaryNandDevice].nSctsPerPg;
	pFlashInfo->dwBytesPerBlock = (pFlashInfo->wSectorsPerBlock * pFlashInfo->wDataBytesPerSector);

	RETAILMSG(1, (TEXT("NUMBLOCKS : %d(0x%x), SECTORSPERBLOCK = %d(0x%x), BYTESPERSECTOR = %d(0x%x) \r\n"), pFlashInfo->dwNumBlocks, pFlashInfo->dwNumBlocks, pFlashInfo->wSectorsPerBlock, pFlashInfo->wSectorsPerBlock, pFlashInfo->wDataBytesPerSector, pFlashInfo->wDataBytesPerSector));

	SetKMode(bLastMode);
	return TRUE;
}

#if MAGNETO
BOOL  FMD_GetInfoEx(PFlashInfoEx pFlashInfo, PDWORD pdwNumRegions)
{
    // Temp
    RETAILMSG(1, (L"FMD_GetInfoEx enter.\r\n"));

    
    if (!pdwNumRegions) 
    {
        return FALSE;
    }
    
    if (!pFlashInfo)
    {
        // Return required buffer size to caller
        *pdwNumRegions = g_dwNumRegions;
        return TRUE;
    }
    
    if (*pdwNumRegions < g_dwNumRegions)
    {
        *pdwNumRegions = g_dwNumRegions;
        DEBUGMSG (1, (TEXT("FMD_GetInfoEx: Insufficient buffer for number of regions")));
        return FALSE;
    }

    memcpy (pFlashInfo->region, g_pRegionTable, g_dwNumRegions * sizeof(FlashRegion));

    // Temp
    for (DWORD iRegion = 0; iRegion < g_dwNumRegions; iRegion++) {
        RETAILMSG(1, (L"Type=%d, StartP=0x%x, NumP=0x%x, NumL=0x%x, Sec/Blk=0x%x, B/Blk=0x%x, Compact=%d.\r\n", 
            g_pRegionTable[iRegion].regionType,
            g_pRegionTable[iRegion].dwStartPhysBlock,
            g_pRegionTable[iRegion].dwNumPhysBlocks,
            g_pRegionTable[iRegion].dwNumLogicalBlocks,
            g_pRegionTable[iRegion].dwSectorsPerBlock,
            g_pRegionTable[iRegion].dwBytesPerBlock,
            g_pRegionTable[iRegion].dwCompactBlocks));

    }

    *pdwNumRegions = g_dwNumRegions;

    pFlashInfo->cbSize					= sizeof(FlashInfoEx);
    pFlashInfo->flashType				= NAND;
    pFlashInfo->dwNumBlocks				= wNUM_BLOCKS;
    pFlashInfo->dwDataBytesPerSector	= NAND_PAGE_SIZE * astNandSpec[dwPrimaryNandDevice].nSctsPerPg;
    pFlashInfo->dwNumRegions			= g_dwNumRegions;

    return(TRUE);
}

BOOL FMD_GetOEMReservedByte(SECTOR_ADDR physicalSectorAddr, PBYTE pOEMReserved)
{
	if ( physicalSectorAddr < (unsigned)wPRIMARY_NAND_BLOCKS*PAGES_PER_BLOCK )		// First NAND Flash
	{
		if ( astNandSpec[dwPrimaryNandDevice].nSctsPerPg == 4 )
			FMD_LB_GetOEMReservedByte( physicalSectorAddr,  pOEMReserved, USE_NFCE);
		else
			FMD_SB_GetOEMReservedByte( physicalSectorAddr,  pOEMReserved, USE_NFCE);
	}
	else
	{
		if ( astNandSpec[dwSecondaryNandDevice].nSctsPerPg == 4 )
			FMD_LB_GetOEMReservedByte( physicalSectorAddr-wPRIMARY_NAND_BLOCKS*PAGES_PER_BLOCK,  pOEMReserved, USE_GPIO);
		else
			FMD_SB_GetOEMReservedByte( physicalSectorAddr-wPRIMARY_NAND_BLOCKS*PAGES_PER_BLOCK,  pOEMReserved, USE_GPIO);
	}
	
	return TRUE;
}

//  FMD_SetOEMReservedByte
//
//  Sets the OEM reserved byte (for metadata) for the specified physical sector.
//
BOOL FMD_SetOEMReservedByte(SECTOR_ADDR physicalSectorAddr, BYTE bOEMReserved)
{

    BOOL    bRet = TRUE;
	if ( physicalSectorAddr < (unsigned)wPRIMARY_NAND_BLOCKS*PAGES_PER_BLOCK )		// First NAND Flash
	{
		if ( astNandSpec[dwPrimaryNandDevice].nSctsPerPg == 4 )
			bRet = FMD_LB_SetOEMReservedByte(physicalSectorAddr, bOEMReserved, USE_NFCE);
		else
			bRet = FMD_SB_SetOEMReservedByte(physicalSectorAddr, bOEMReserved, USE_NFCE);
	}
	else
	{
		if ( astNandSpec[dwSecondaryNandDevice].nSctsPerPg == 4 )
			bRet = FMD_LB_SetOEMReservedByte(physicalSectorAddr-wPRIMARY_NAND_BLOCKS*PAGES_PER_BLOCK, bOEMReserved, USE_GPIO);
		else
			bRet = FMD_SB_SetOEMReservedByte(physicalSectorAddr-wPRIMARY_NAND_BLOCKS*PAGES_PER_BLOCK, bOEMReserved, USE_GPIO);
	}
	
    return bRet;
}




#ifndef NOSYSCALL
//  We don't have to build the following interface functions for the
//  bootloader.
//

//  FMD_PowerUp
//
//  Performs any necessary powerup procedures...
//
VOID FMD_PowerUp(VOID)
{
	// Set up initial flash controller configuration.
	s6410NAND->NFCONF = (NAND_TACLS  <<  12) | (NAND_TWRPH0 <<  8) | (NAND_TWRPH1 <<  4);
	s6410NAND->NFCONT = (0<<17)|(0<<16)|(0<<10)|(0<<9)|(0<<8)|(1<<7)|(1<<6)|(1<<5)|(1<<4)|(0x3<<1)|(1<<0);
	s6410NAND->NFSTAT = (1<<4);
}

//  FMD_PowerDown
//
//  Performs any necessary powerdown procedures...
//
VOID FMD_PowerDown(VOID)
{

}

//  FMD_OEMIoControl
//
//  Used for any OEM defined IOCTL operations
//
BOOL FMD_OEMIoControl(DWORD dwIoControlCode, PBYTE pInBuf, DWORD nInBufSize, PBYTE pOutBuf, DWORD nOutBufSize, PDWORD pBytesReturned)
{
#if	MAGNETO
    BSP_ARGS *pBSPArgs = ((BSP_ARGS *) IMAGE_SHARE_ARGS_UA_START);
#endif

	RETAILMSG(1,(TEXT("[FMD] FMD_OEMIoControl(0x%08x)\r\n"), dwIoControlCode));

	switch(dwIoControlCode)
	{

	    case IOCTL_FMD_GET_INTERFACE:
	    {
		RETAILMSG(1, (TEXT("[FMD] FMD_OEMIoControl() : IOCTL_FMD_GET_INTERFACE\r\n")));

	        if (!pOutBuf || nOutBufSize < sizeof(FMDInterface))
	        {
			RETAILMSG(1, (TEXT("[FMD:ERR] FMD_OEMIoControl() : Invalid Parameter\r\n")));
			return FALSE;
	        }    
	
	        PFMDInterface pInterface = (PFMDInterface)pOutBuf;

	        pInterface->cbSize = sizeof(FMDInterface);
	        pInterface->pInit = FMD_Init;
	        pInterface->pDeInit = FMD_Deinit;
	        pInterface->pGetInfo = FMD_GetInfo;        
	        pInterface->pGetInfoEx = FMD_GetInfoEx;
	        pInterface->pGetBlockStatus = FMD_GetBlockStatus;     

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?