UEFI源码学习3.7 - NorFlashDxe

AARCH64 QEMU上的NOR FLASH驱动位于ArmPlatFromPkg/Drivers/NorFlashDxe目录下。

1. QEMU CFI NOR FLASH

在分析QEMU 上CFI NOR FLASH之前,需要知道一些QEMU CFI NOR FLASH的参数。
FLASH ARRAY地址: 0x04000000 - 0x08000000
寄存器地址:0x04000000。寄存器和FLASH ARRAY用的同一个地址,当写入0xFF命令到寄存器时,操作的是FLASH ARRAY。否则操作的寄存器。
命令表

命令功能
0x00Reset
0x10单字节写入
0x20Block擦除
0x28Block擦除
0x40单字节写入
0x50清除Status寄存器
0x60Block 加锁/解锁
0x70读取Status寄存器
0xe8写一个Block
0x90读取Device ID 寄存器
0x90Query CFI
0xff进入读FLASH ARRAY 模式

命令定义
ArmPlatFromPkg/Drivers/NorFlashDxe/NorFlash.h

#define P30_MAX_BUFFER_SIZE_IN_BYTES  ((UINTN)128)
#define P30_MAX_BUFFER_SIZE_IN_WORDS  (P30_MAX_BUFFER_SIZE_IN_BYTES/((UINTN)4))
#define MAX_BUFFERED_PROG_ITERATIONS  10000000
#define BOUNDARY_OF_32_WORDS          0x7F

// CFI Addresses
#define P30_CFI_ADDR_QUERY_UNIQUE_QRY  0x10
#define P30_CFI_ADDR_VENDOR_ID         0x13

// CFI Data
#define CFI_QRY  0x00595251

// READ Commands
#define P30_CMD_READ_DEVICE_ID         0x0090
#define P30_CMD_READ_STATUS_REGISTER   0x0070
#define P30_CMD_CLEAR_STATUS_REGISTER  0x0050
#define P30_CMD_READ_ARRAY             0x00FF
#define P30_CMD_READ_CFI_QUERY         0x0098

// WRITE Commands
#define P30_CMD_WORD_PROGRAM_SETUP            0x0040
#define P30_CMD_ALTERNATE_WORD_PROGRAM_SETUP  0x0010
#define P30_CMD_BUFFERED_PROGRAM_SETUP        0x00E8
#define P30_CMD_BUFFERED_PROGRAM_CONFIRM      0x00D0
#define P30_CMD_BEFP_SETUP                    0x0080
#define P30_CMD_BEFP_CONFIRM                  0x00D0

// ERASE Commands
#define P30_CMD_BLOCK_ERASE_SETUP    0x0020
#define P30_CMD_BLOCK_ERASE_CONFIRM  0x00D0

// SUSPEND Commands
#define P30_CMD_PROGRAM_OR_ERASE_SUSPEND  0x00B0
#define P30_CMD_SUSPEND_RESUME            0x00D0

// BLOCK LOCKING / UNLOCKING Commands
#define P30_CMD_LOCK_BLOCK_SETUP  0x0060
#define P30_CMD_LOCK_BLOCK        0x0001
#define P30_CMD_UNLOCK_BLOCK      0x00D0
#define P30_CMD_LOCK_DOWN_BLOCK   0x002F

// PROTECTION Commands
#define P30_CMD_PROGRAM_PROTECTION_REGISTER_SETUP  0x00C0

// CONFIGURATION Commands
#define P30_CMD_READ_CONFIGURATION_REGISTER_SETUP  0x0060
#define P30_CMD_READ_CONFIGURATION_REGISTER        0x0003

2. 数据结构

用于描述NOR FLASH的结构叫做NOR_FLASH_INSTACE,其结构体布局如下
在这里插入图片描述

struct _NOR_FLASH_INSTANCE {
  UINT32                                 Signature;				--> 一个4字节的签名为"norx", x代表数字012
  EFI_HANDLE                             Handle;				--> 对应的image handle

  UINTN                                  DeviceBaseAddress;		--> Nor Flash寄存器的地址
  UINTN                                  RegionBaseAddress;		--> Nor Flash存储区的地址
  UINTN                                  Size;					-->	Nor Flash的大小
  EFI_LBA                                StartLba;				--> 起始Logic Block Address
	
  EFI_BLOCK_IO_PROTOCOL                  BlockIoProtocol;		--> 一个EFI_BLOCK_IO_PROTOCOL用于提供Block IO 服务
  EFI_BLOCK_IO_MEDIA                     Media;					--> 描述块设备的结构体
  EFI_DISK_IO_PROTOCOL                   DiskIoProtocol;		--> 一个EFI_DISK_IO_PROTOCOL用于提供Disk IO服务
		
  EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL    FvbProtocol;			--> 一个EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL用于提供Fv2服务
  VOID                                   *ShadowBuffer;			--> 缓存buffer
	
  NOR_FLASH_DEVICE_PATH                  DevicePath;			--> 设备路径对象
};

3. 初始化

NorFlashInitialise 是NorFlashDxe的入口函数,这里面初始化了NorFlashDxe。

  1. 因为是QEMU,NorFlashPlatformInitialization 这里啥也没干。实际的硬件上应该需要初始化clock,power, iomux等等。
  2. NorFlashPlatformGetDevices 从FDT(设备树文件上获取NOR FLASH的实例以及个数)。这里例子中只有一个NOR FLASH,所以mNorFlashDeviceCount = 1。
  3. 调用AllocateRuntimePool 为每一个分配NOR_FLASH_INSTANCE实例所需要的内存。
  4. 根据PCD获取ContainVariableStorage信息。
  5. 调用NorFlashCreateInstance 为每一个NOR FLASH的对象成员初始化。
  6. 创建Event,用于在虚拟地址改变的时候回调NorFlashVirtualNotifyEvent函数。
EFI_STATUS
EFIAPI
NorFlashInitialise (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS             Status;
  UINT32                 Index;
  NOR_FLASH_DESCRIPTION  *NorFlashDevices;
  BOOLEAN                ContainVariableStorage;
  //1. 因为是QEMU,NorFlashPlatformInitialization 这里啥也没干。实际的硬件上应该需要初始化clock,power, iomux等等。
  Status = NorFlashPlatformInitialization ();
  //2. NorFlashPlatformGetDevices 从FDT(设备树文件上获取NOR FLASH的实例以及个数)。这里例子中只有一个NOR FLASH,所以mNorFlashDeviceCount = 1。
  Status = NorFlashPlatformGetDevices (&NorFlashDevices, &mNorFlashDeviceCount);
  //3. 调用AllocateRuntimePool 为每一个分配NOR_FLASH_INSTANCE实例。
  mNorFlashInstances = AllocateRuntimePool (sizeof (NOR_FLASH_INSTANCE *) * mNorFlashDeviceCount);

  for (Index = 0; Index < mNorFlashDeviceCount; Index++) {
    // Check if this NOR Flash device contain the variable storage region
	//4. 根据PCD获取ContainVariableStorage信息。
    if (PcdGet64 (PcdFlashNvStorageVariableBase64) != 0) {
      ContainVariableStorage =
        (NorFlashDevices[Index].RegionBaseAddress <= PcdGet64 (PcdFlashNvStorageVariableBase64)) &&
        (PcdGet64 (PcdFlashNvStorageVariableBase64) + PcdGet32 (PcdFlashNvStorageVariableSize) <=
         NorFlashDevices[Index].RegionBaseAddress + NorFlashDevices[Index].Size);
    } else {
      ContainVariableStorage =
        (NorFlashDevices[Index].RegionBaseAddress <= PcdGet32 (PcdFlashNvStorageVariableBase)) &&
        (PcdGet32 (PcdFlashNvStorageVariableBase) + PcdGet32 (PcdFlashNvStorageVariableSize) <=
         NorFlashDevices[Index].RegionBaseAddress + NorFlashDevices[Index].Size);
    }
	//5. 调用NorFlashCreateInstance 为每一个NOR FLASH的对象成员初始化。
    Status = NorFlashCreateInstance (
               NorFlashDevices[Index].DeviceBaseAddress,
               NorFlashDevices[Index].RegionBaseAddress,
               NorFlashDevices[Index].Size,
               Index,
               NorFlashDevices[Index].BlockSize,
               ContainVariableStorage,
               &mNorFlashInstances[Index]
               );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "NorFlashInitialise: Fail to create instance for NorFlash[%d]\n", Index));
    }
  }

  //6. 创建Event,用于在虚拟地址改变的时候回调NorFlashVirtualNotifyEvent函数。
  Status = gBS->CreateEventEx (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  NorFlashVirtualNotifyEvent,
                  NULL,
                  &gEfiEventVirtualAddressChangeGuid,
                  &mNorFlashVirtualAddrChangeEvent
                  );
  ASSERT_EFI_ERROR (Status);

  return Status;
}

NOR FLASH对象的成员主要在NorFlashCreateInstance中初始化。

  1. 首先调用AllocateRuntimeCopyPool把mNorFlashInstanceTemplate中的内容复制到NOR_FLASH_INSTANCE上。可以看到mNorFlashInstanceTemplate主要是一些函数指针。
  2. 根据参数初始化NOR_FLASH_INSTANCE各个成员。
成员名
DeviceBaseAddress0x04000000
RegionBaseAddress0x04000000
Size0x04000000
Media.MediaId0
Media.BlockSize0x40000
DevicePath.Vendor.Guid93E34C7E-B50E-11DF-9223-2443DFD72085
  1. 调用AllocateRuntimePool分配缓存buffer。
  2. NorFlashFvbInitialize 初始化NOR FLASH上的FV。
  3. 调用InstallMultipleProtocolInterfaces 安装以下Protocol:gEfiDevicePathProtocolGuid,gEfiBlockIoProtocolGuid, gEfiFirmwareVolumeBlockProtocolGuid,gEfiDiskIoProtocolGuid
EFI_STATUS
NorFlashCreateInstance (
  IN UINTN                NorFlashDeviceBase,
  IN UINTN                NorFlashRegionBase,
  IN UINTN                NorFlashSize,
  IN UINT32               Index,
  IN UINT32               BlockSize,
  IN BOOLEAN              SupportFvb,
  OUT NOR_FLASH_INSTANCE  **NorFlashInstance
  )
{
  EFI_STATUS          Status;
  NOR_FLASH_INSTANCE  *Instance;

  ASSERT (NorFlashInstance != NULL);
  //1. 首先调用AllocateRuntimeCopyPool把mNorFlashInstanceTemplate中的内容复制到NOR_FLASH_INSTANCE上。可以看到mNorFlashInstanceTemplate主要是一些函数指针。
  Instance = AllocateRuntimeCopyPool (sizeof (NOR_FLASH_INSTANCE), &mNorFlashInstanceTemplate);
  if (Instance == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }
  //2. 根据参数初始化NOR_FLASH_INSTANCE各个成员。
  Instance->DeviceBaseAddress = NorFlashDeviceBase;
  Instance->RegionBaseAddress = NorFlashRegionBase;
  Instance->Size              = NorFlashSize;

  Instance->BlockIoProtocol.Media = &Instance->Media;
  Instance->Media.MediaId         = Index;
  Instance->Media.BlockSize       = BlockSize;
  Instance->Media.LastBlock       = (NorFlashSize / BlockSize)-1;

  CopyGuid (&Instance->DevicePath.Vendor.Guid, &gEfiCallerIdGuid);
  Instance->DevicePath.Index = (UINT8)Index;
  //3. 调用AllocateRuntimePool分配缓存buffer。
  Instance->ShadowBuffer = AllocateRuntimePool (BlockSize);
  if (Instance->ShadowBuffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }


   //4. NorFlashFvbInitialize 初始化NOR FLASH上的FV。
   NorFlashFvbInitialize (Instance);
   //5. 安装以下Protoco
   Status = gBS->InstallMultipleProtocolInterfaces (
                   &Instance->Handle,
                   &gEfiDevicePathProtocolGuid,
                   &Instance->DevicePath,
                   &gEfiBlockIoProtocolGuid,
                   &Instance->BlockIoProtocol,
                   &gEfiFirmwareVolumeBlockProtocolGuid,
                   &Instance->FvbProtocol,
                   &gEfiDiskIoProtocolGuid,
                   &Instance->DiskIoProtocol,
                   NULL
                   );

  ...

  *NorFlashInstance = Instance;
  return Status;
}

mNorFlashInstanceTemplate的定义如下,实现了一些Protocol的函数。

NOR_FLASH_INSTANCE  mNorFlashInstanceTemplate = {
  NOR_FLASH_SIGNATURE, // Signature
  NULL,                // Handle ... NEED TO BE FILLED

  0, // DeviceBaseAddress ... NEED TO BE FILLED
  0, // RegionBaseAddress ... NEED TO BE FILLED
  0, // Size ... NEED TO BE FILLED
  0, // StartLba

  {
    EFI_BLOCK_IO_PROTOCOL_REVISION2, // Revision
    NULL,                            // Media ... NEED TO BE FILLED
    NorFlashBlockIoReset,            // Reset;
    NorFlashBlockIoReadBlocks,       // ReadBlocks
    NorFlashBlockIoWriteBlocks,      // WriteBlocks
    NorFlashBlockIoFlushBlocks       // FlushBlocks
  }, // BlockIoProtocol

  {
    0,     // MediaId ... NEED TO BE FILLED
    FALSE, // RemovableMedia
    TRUE,  // MediaPresent
    FALSE, // LogicalPartition
    FALSE, // ReadOnly
    FALSE, // WriteCaching;
    0,     // BlockSize ... NEED TO BE FILLED
    4,     //  IoAlign
    0,     // LastBlock ... NEED TO BE FILLED
    0,     // LowestAlignedLba
    1,     // LogicalBlocksPerPhysicalBlock
  }, // Media;

  {
    EFI_DISK_IO_PROTOCOL_REVISION, // Revision
    NorFlashDiskIoReadDisk,        // ReadDisk
    NorFlashDiskIoWriteDisk        // WriteDisk
  },

  {
    FvbGetAttributes,      // GetAttributes
    FvbSetAttributes,      // SetAttributes
    FvbGetPhysicalAddress, // GetPhysicalAddress
    FvbGetBlockSize,       // GetBlockSize
    FvbRead,               // Read
    FvbWrite,              // Write
    FvbEraseBlocks,        // EraseBlocks
    NULL,                  // ParentHandle
  },    //  FvbProtoccol;
  NULL, // ShadowBuffer
  {
    {
      {
        HARDWARE_DEVICE_PATH,
        HW_VENDOR_DP,
        {
          (UINT8)(OFFSET_OF (NOR_FLASH_DEVICE_PATH, End)),
          (UINT8)(OFFSET_OF (NOR_FLASH_DEVICE_PATH, End) >> 8)
        }
      },
      { 0x0,                               0x0, 0x0, { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }
      },                                                             // GUID ... NEED TO BE FILLED
    },
    0, // Index
    {
      END_DEVICE_PATH_TYPE,
      END_ENTIRE_DEVICE_PATH_SUBTYPE,
      { sizeof (EFI_DEVICE_PATH_PROTOCOL), 0 }
    }
  }   // DevicePath
};

NorFlashFvbInitialize 初始化NOR FLASH上的FV头。

  1. 调用AddMemorySpace和SetMemorySpaceAttributes 向DxeCore上报NOR FLASH的内存空间。
  2. 调用ValidateFvHeader查看NOR FLASH上是否存在FV。
  3. 如果不存在FV的话,就在NOR FLASH上初始化FV,这个FV是用来存Non Volatile Variable的。
  4. 安装Protocol:gEdkiiNvVarStoreFormattedGuid。
  5. 创建Event,在虚拟地址改变时回调FvbVirtualNotifyEvent。
EFI_STATUS
EFIAPI
NorFlashFvbInitialize (
  IN NOR_FLASH_INSTANCE  *Instance
  )
{
  EFI_STATUS     Status;
  UINT32         FvbNumLba;
  EFI_BOOT_MODE  BootMode;
  UINTN          RuntimeMmioRegionSize;
  ...
  //1. 调用AddMemorySpace和SetMemorySpaceAttributes 向DxeCore上报NOR FLASH的内存空间。
  RuntimeMmioRegionSize = (Instance->RegionBaseAddress - Instance->DeviceBaseAddress) + Instance->Size;
  Status = gDS->AddMemorySpace (
                  EfiGcdMemoryTypeMemoryMappedIo,
                  Instance->DeviceBaseAddress,
                  RuntimeMmioRegionSize,
                  EFI_MEMORY_UC | EFI_MEMORY_RUNTIME
                  );

  Status = gDS->SetMemorySpaceAttributes (
                  Instance->DeviceBaseAddress,
                  RuntimeMmioRegionSize,
                  EFI_MEMORY_UC | EFI_MEMORY_RUNTIME
                  );
  //根据PCD获取NvStorageVariable存放的基地址
  mFlashNvStorageVariableBase = (PcdGet64 (PcdFlashNvStorageVariableBase64) != 0) ?
                                PcdGet64 (PcdFlashNvStorageVariableBase64) : PcdGet32 (PcdFlashNvStorageVariableBase);
 
  //2. 调用ValidateFvHeader查看NOR FLASH上是否存在FV。
  Status = ValidateFvHeader (Instance);


  // Install the Default FVB header if required
  if (EFI_ERROR (Status)) {
    // There is no valid header, so time to install one.
    //3. 如果不存在FV的话,就在NOR FLASH上初始化FV,这个FV是用来存Non Volatile Variable的。
    // 擦除Flash
    FvbNumLba = (PcdGet32 (PcdFlashNvStorageVariableSize) + PcdGet32 (PcdFlashNvStorageFtwWorkingSize) + PcdGet32 (PcdFlashNvStorageFtwSpareSize)) / Instance->Media.BlockSize;
    Status = FvbEraseBlocks (&Instance->FvbProtocol, (EFI_LBA)0, FvbNumLba, EFI_LBA_LIST_TERMINATOR);

    // 安装NvStorageVariable FV头
    Status = InitializeFvAndVariableStoreHeaders (Instance);

  }
  //4. 安装Protocol:gEdkiiNvVarStoreFormattedGuid。
  Status = gBS->InstallProtocolInterface (
                  &gImageHandle,
                  &gEdkiiNvVarStoreFormattedGuid,
                  EFI_NATIVE_INTERFACE,
                  NULL
                  );

  //5. 创建Event,在虚拟地址改变时回调FvbVirtualNotifyEvent。
  Status = gBS->CreateEventEx (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  FvbVirtualNotifyEvent,
                  NULL,
                  &gEfiEventVirtualAddressChangeGuid,
                  &mFvbVirtualAddrChangeEvent
                  );
  ASSERT_EFI_ERROR (Status);

  return Status;
}

InitializeFvAndVariableStoreHeaders 很简单,就是根据PCD里读出NvStrorageVariable FV的各个参数,然后生成FV头,最终调用FvbWrite (&Instance->FvbProtocol, 0, 0, &HeadersLength, Headers);把FV头写入到NOR FLASH上。

EFI_STATUS
InitializeFvAndVariableStoreHeaders (
  IN NOR_FLASH_INSTANCE  *Instance
  )
{
  ...
  FirmwareVolumeHeader = (EFI_FIRMWARE_VOLUME_HEADER *)Headers;
  CopyGuid (&FirmwareVolumeHeader->FileSystemGuid, &gEfiSystemNvDataFvGuid);
  FirmwareVolumeHeader->FvLength =
    PcdGet32 (PcdFlashNvStorageVariableSize) +
    PcdGet32 (PcdFlashNvStorageFtwWorkingSize) +
    PcdGet32 (PcdFlashNvStorageFtwSpareSize);
  FirmwareVolumeHeader->Signature  = EFI_FVH_SIGNATURE;
  FirmwareVolumeHeader->Attributes = (EFI_FVB_ATTRIBUTES_2)(
                                                            EFI_FVB2_READ_ENABLED_CAP   | // Reads may be enabled
                                                            EFI_FVB2_READ_STATUS        | // Reads are currently enabled
                                                            EFI_FVB2_STICKY_WRITE       | // A block erase is required to flip bits into EFI_FVB2_ERASE_POLARITY
                                                            EFI_FVB2_MEMORY_MAPPED      | // It is memory mapped
                                                            EFI_FVB2_ERASE_POLARITY     | // After erasure all bits take this value (i.e. '1')
                                                            EFI_FVB2_WRITE_STATUS       | // Writes are currently enabled
                                                            EFI_FVB2_WRITE_ENABLED_CAP    // Writes may be enabled
                                                            );
  FirmwareVolumeHeader->HeaderLength          = sizeof (EFI_FIRMWARE_VOLUME_HEADER) + sizeof (EFI_FV_BLOCK_MAP_ENTRY);
  FirmwareVolumeHeader->Revision              = EFI_FVH_REVISION;
  FirmwareVolumeHeader->BlockMap[0].NumBlocks = Instance->Media.LastBlock + 1;
  FirmwareVolumeHeader->BlockMap[0].Length    = Instance->Media.BlockSize;
  FirmwareVolumeHeader->BlockMap[1].NumBlocks = 0;
  FirmwareVolumeHeader->BlockMap[1].Length    = 0;
  FirmwareVolumeHeader->Checksum              = CalculateCheckSum16 ((UINT16 *)FirmwareVolumeHeader, FirmwareVolumeHeader->HeaderLength);

  //
  // VARIABLE_STORE_HEADER
  //
  VariableStoreHeader = (VARIABLE_STORE_HEADER *)((UINTN)Headers + FirmwareVolumeHeader->HeaderLength);
  CopyGuid (&VariableStoreHeader->Signature, &gEfiAuthenticatedVariableGuid);
  VariableStoreHeader->Size   = PcdGet32 (PcdFlashNvStorageVariableSize) - FirmwareVolumeHeader->HeaderLength;
  VariableStoreHeader->Format = VARIABLE_STORE_FORMATTED;
  VariableStoreHeader->State  = VARIABLE_STORE_HEALTHY;

  // Install the combined super-header in the NorFlash
  Status = FvbWrite (&Instance->FvbProtocol, 0, 0, &HeadersLength, Headers);

  FreePool (Headers);
  return Status;
}

4. 读写实现

4.1 NOR FLASH读写

ArmPlatFromPkg/Drivers/NorFlashDxe/NorFlash.c
单字节写入NorFlashWriteSingleWord

  1. 向NOR FLASH发送P30_CMD_WORD_PROGRAM_SETUP命令。
  2. 把数据WriteData写入NOR FLASH地址WordAddress。
  3. 读取Status 寄存器,等待写入完成。
  4. 发送P30_CMD_READ_ARRAY命令将NOR FLASH设置为READ ARRAY模式
EFI_STATUS
NorFlashWriteSingleWord (
  IN NOR_FLASH_INSTANCE  *Instance,
  IN UINTN               WordAddress,
  IN UINT32              WriteData
  )
{
  EFI_STATUS  Status;
  UINT32      StatusRegister;

  Status = EFI_SUCCESS;

  //1. 向NOR FLASH发送P30_CMD_WORD_PROGRAM_SETUP命令
  SEND_NOR_COMMAND (WordAddress, 0, P30_CMD_WORD_PROGRAM_SETUP);

  //2. 把数据WriteData写入NOR FLASH地址WordAddress。
  MmioWrite32 (WordAddress, WriteData);

  //3. 读取Status 寄存器,等待写入完成。
  do {
    // Prepare to read the status register
    StatusRegister = NorFlashReadStatusRegister (Instance, WordAddress);
    // The chip is busy while the WRITE bit is not asserted
  } while ((StatusRegister & P30_SR_BIT_WRITE) != P30_SR_BIT_WRITE);

  // Perform a full status check:
  // Mask the relevant bits of Status Register.
  // Everything should be zero, if not, we have a problem

  if (StatusRegister & P30_SR_BIT_VPP) {
    DEBUG ((DEBUG_ERROR, "NorFlashWriteSingleWord(WordAddress:0x%X): VPP Range Error\n", WordAddress));
    Status = EFI_DEVICE_ERROR;
  }

  if (StatusRegister & P30_SR_BIT_PROGRAM) {
    DEBUG ((DEBUG_ERROR, "NorFlashWriteSingleWord(WordAddress:0x%X): Program Error\n", WordAddress));
    Status = EFI_DEVICE_ERROR;
  }

  if (StatusRegister & P30_SR_BIT_BLOCK_LOCKED) {
    DEBUG ((DEBUG_ERROR, "NorFlashWriteSingleWord(WordAddress:0x%X): Device Protect Error\n", WordAddress));
    Status = EFI_DEVICE_ERROR;
  }

  if (!EFI_ERROR (Status)) {
    // Clear the Status Register
    SEND_NOR_COMMAND (Instance->DeviceBaseAddress, 0, P30_CMD_CLEAR_STATUS_REGISTER);
  }

  //4. 将NOR FLASH设置为READ ARRAY模式
  SEND_NOR_COMMAND (Instance->DeviceBaseAddress, 0, P30_CMD_READ_ARRAY);

  return Status;
}

其他的NorFlashWriteBlocks、NorFlashWriteSingleBlock和NorFlashWriteBuffer的流程也是类似的:发命令 ->写数据 -> 读Status寄存器 -> 设置为READ ARRAY模式。
只是发送的命令不同。

读的实现就简单了。NOR FLASH是memory map的设备,直接从0x04000000地址上读就能读到NOR FLASH的数据。

  1. 做一堆参数检查,获取NOR FLASH ARRAY的起始地址。
  2. 发送P30_CMD_READ_ARRAY命令把NOR FLASH设置为READY ARRAY模式。
  3. 把数据从对应的FLASH地址空间复制到目的地址上去。
EFI_STATUS
NorFlashRead (
  IN NOR_FLASH_INSTANCE  *Instance,
  IN EFI_LBA             Lba,
  IN UINTN               Offset,
  IN UINTN               BufferSizeInBytes,
  OUT VOID               *Buffer
  )
{
  UINTN  StartAddress;
  ...
  // 1. 做一堆参数检查,获取NOR FLASH ARRAY的起始地址。
  StartAddress = GET_NOR_BLOCK_ADDRESS (
                   Instance->RegionBaseAddress,
                   Lba,
                   Instance->Media.BlockSize
                   );

  // 2. 发送P30_CMD_READ_ARRAY命令把NOR FLASH设置为READY ARRAY模式。
  SEND_NOR_COMMAND (Instance->DeviceBaseAddress, 0, P30_CMD_READ_ARRAY);

  // 3. 把数据从对应的FLASH地址空间复制到目的地址上去。
  AlignedCopyMem (Buffer, (VOID *)(StartAddress + Offset), BufferSizeInBytes);

  return EFI_SUCCESS;
}

4.2 Block IO Protocol实现

ArmPlatFromPkg/Drivers/NorFlashDxe/NorFlashBlockIo.c,这里面就是实现了BlockIO Protocol的相关函数,底层调用还是NorFlash.c上的函数。

  • NorFlashBlockIoReadBlocks 做了一堆参数检查后调用了NorFlashReadBlocks 去读数据。
  • NorFlashBlockIoWriteBlocks 做了一堆检查后调用NorFlashWriteBlocks 写入数据。

EFI_STATUS
EFIAPI
NorFlashBlockIoReadBlocks (
  IN  EFI_BLOCK_IO_PROTOCOL  *This,
  IN  UINT32                 MediaId,
  IN  EFI_LBA                Lba,
  IN  UINTN                  BufferSizeInBytes,
  OUT VOID                   *Buffer
  )
{
  NOR_FLASH_INSTANCE  *Instance;
  EFI_STATUS          Status;
  EFI_BLOCK_IO_MEDIA  *Media;

  if (This == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Instance = INSTANCE_FROM_BLKIO_THIS (This);
  Media    = This->Media;

  DEBUG ((DEBUG_BLKIO, "NorFlashBlockIoReadBlocks(MediaId=0x%x, Lba=%ld, BufferSize=0x%x bytes (%d kB), BufferPtr @ 0x%08x)\n", MediaId, Lba, BufferSizeInBytes, Buffer));

  if (!Media) {
    Status = EFI_INVALID_PARAMETER;
  } else if (!Media->MediaPresent) {
    Status = EFI_NO_MEDIA;
  } else if (Media->MediaId != MediaId) {
    Status = EFI_MEDIA_CHANGED;
  } else if ((Media->IoAlign > 2) && (((UINTN)Buffer & (Media->IoAlign - 1)) != 0)) {
    Status = EFI_INVALID_PARAMETER;
  } else {
    Status = NorFlashReadBlocks (Instance, Lba, BufferSizeInBytes, Buffer);
  }

  return Status;
}


EFI_STATUS
EFIAPI
NorFlashBlockIoWriteBlocks (
  IN  EFI_BLOCK_IO_PROTOCOL  *This,
  IN  UINT32                 MediaId,
  IN  EFI_LBA                Lba,
  IN  UINTN                  BufferSizeInBytes,
  IN  VOID                   *Buffer
  )
{
  NOR_FLASH_INSTANCE  *Instance;
  EFI_STATUS          Status;

  Instance = INSTANCE_FROM_BLKIO_THIS (This);

  DEBUG ((DEBUG_BLKIO, "NorFlashBlockIoWriteBlocks(MediaId=0x%x, Lba=%ld, BufferSize=0x%x bytes (%d kB), BufferPtr @ 0x%08x)\n", MediaId, Lba, BufferSizeInBytes, Buffer));

  if ( !This->Media->MediaPresent ) {
    Status = EFI_NO_MEDIA;
  } else if ( This->Media->MediaId != MediaId ) {
    Status = EFI_MEDIA_CHANGED;
  } else if ( This->Media->ReadOnly ) {
    Status = EFI_WRITE_PROTECTED;
  } else {
    Status = NorFlashWriteBlocks (Instance, Lba, BufferSizeInBytes, Buffer);
  }

  return Status;
}

4.3 Disk IO Protocol实现

Disk IO 与Block IO 也是类似的,最终调用NorFlash.c中的底层函数去实现。但是它在外层包装了一点外层逻辑以达到任意字节大小的写入功能。

EFI_STATUS
EFIAPI
NorFlashDiskIoReadDisk (
  IN EFI_DISK_IO_PROTOCOL  *This,
  IN UINT32                MediaId,
  IN UINT64                DiskOffset,
  IN UINTN                 BufferSize,
  OUT VOID                 *Buffer
  )
{
  NOR_FLASH_INSTANCE  *Instance;
  UINT32              BlockSize;
  UINT32              BlockOffset;
  EFI_LBA             Lba;

  Instance = INSTANCE_FROM_DISKIO_THIS (This);

  if (MediaId != Instance->Media.MediaId) {
    return EFI_MEDIA_CHANGED;
  }

  BlockSize = Instance->Media.BlockSize;
  Lba       = (EFI_LBA)DivU64x32Remainder (DiskOffset, BlockSize, &BlockOffset);

  return NorFlashRead (Instance, Lba, BlockOffset, BufferSize, Buffer);
}


EFI_STATUS
EFIAPI
NorFlashDiskIoWriteDisk (
  IN EFI_DISK_IO_PROTOCOL  *This,
  IN UINT32                MediaId,
  IN UINT64                DiskOffset,
  IN UINTN                 BufferSize,
  IN VOID                  *Buffer
  )
{
  NOR_FLASH_INSTANCE  *Instance;
  UINT32              BlockSize;
  UINT32              BlockOffset;
  EFI_LBA             Lba;
  UINTN               RemainingBytes;
  UINTN               WriteSize;
  EFI_STATUS          Status;

  Instance = INSTANCE_FROM_DISKIO_THIS (This);

  if (MediaId != Instance->Media.MediaId) {
    return EFI_MEDIA_CHANGED;
  }

  BlockSize = Instance->Media.BlockSize;
  Lba       = (EFI_LBA)DivU64x32Remainder (DiskOffset, BlockSize, &BlockOffset);

  RemainingBytes = BufferSize;

  WriteSize = MIN (RemainingBytes, ((DiskOffset | (BlockSize - 1)) + 1) - DiskOffset);

  do {
    if (WriteSize == BlockSize) {
      // Write a full block
      Status = NorFlashWriteFullBlock (Instance, Lba, Buffer, BlockSize / sizeof (UINT32));
    } else {
      // Write a partial block
      Status = NorFlashWriteSingleBlock (Instance, Lba, BlockOffset, &WriteSize, Buffer);
    }

    if (EFI_ERROR (Status)) {
      return Status;
    }

    // Now continue writing either all the remaining bytes or single blocks.
    RemainingBytes -= WriteSize;
    Buffer          = (UINT8 *)Buffer + WriteSize;
    Lba++;
    BlockOffset = 0;
    WriteSize   = MIN (RemainingBytes, BlockSize);
  } while (RemainingBytes);

  return Status;
}

5. 测试代码

测试函数如下,调用NOR FLASH的DiskIO Protocol。

  • 由于当前FD上只有一个NorFlash这个DiskIO设备,所以直接用LocateProtocol去找到唯一的DiskIO以获取NOR FLASH的读写。
  • 分别用DiskIO的方式和直接读FLASH地址0x4000010的方式读取FLASH上的数据。
  • 往FLASH头上写入16个字节的数据。
  • 分别用DiskIO的方式和直接读FLASH地址0x4000000的方式读取FLASH上的数据。
VOID
TestDxeTestNorFlashProtocol()
{
    EFI_STATUS                    Status;
    UINT32 Buffer[128];
    UINT32 *Addr = (UINT32 *)0x4000000;
    DEBUG((DEBUG_INFO, "TestDxeTestNorFlashProtocol\n"));
    EFI_DISK_IO_PROTOCOL         *DiskIo;
    Status = gBS->LocateProtocol(&gEfiDiskIoProtocolGuid, NULL, (VOID **)&DiskIo);
    if (Status != EFI_SUCCESS) {
        DEBUG((DEBUG_INFO, "Locate block io failed\n"));
        return;
    }
    DiskIo->ReadDisk(DiskIo, 0, 0, 512, Buffer);
    DEBUG((DEBUG_INFO, "#######Read by Bufffer:0x%08x 0x%08x 0x%08x 0x%08x\n", Buffer[4], Buffer[5], Buffer[6], Buffer[7]));
    DEBUG((DEBUG_INFO, "#######Read by Address: 0x4000010: 0x%08x 0x%08x 0x%08x 0x%08x\n", *(Addr + 4), *(Addr+5), *(Addr+6), *(Addr + 7)));
    for (UINT32 i = 0;  i < 4; i++) {
        Buffer[i] = i;
    }

    DiskIo->WriteDisk(DiskIo, 0, 0, 16, Buffer);
    DiskIo->ReadDisk(DiskIo, 0, 0, 512, Buffer);
    DEBUG((DEBUG_INFO, "#######Read by Bufffer: 0x%08x 0x%08x 0x%08x 0x%08x\n", Buffer[0], Buffer[1], Buffer[2], Buffer[3]));
    DEBUG((DEBUG_INFO, "#######Read by Address: 0x4000000: 0x%08x 0x%08x 0x%08x 0x%08x\n", *Addr, *(Addr+1), *(Addr+2), *(Addr + 3)));
}

打印的log

TestDxeTestNorFlashProtocol
#######Read by Bufffer:			   0xFFF12B8D 0x4C8B7696 0x472785A9 0x504F5B07
#######Read by Address: 0x4000010: 0xFFF12B8D 0x4C8B7696 0x472785A9 0x504F5B07
#######Read by Bufffer: 		   0x00000000 0x00000001 0x00000002 0x00000003
#######Read by Address: 0x4000000: 0x00000000 0x00000001 0x00000002 0x00000003
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值