Step to UEFI (104)eMMC in the Shell

eMMC 应用在平板电脑上已经有一段时间了,本文给出了一个取得 eMMC CID 的例子。当然,你在其他资料上还会发现一些类似 MMC_INFO 之类的Protocol,但是根据我的实验,目前大多数系统只支持SD HOST IO 这一个 Protocol。此外,强烈建议一定在实体机上进行实验,避免出现费了很大力气编写代码,但是实际上第一步就无法完成的问题。
原理介绍:首先查找系统中的 SD_HOST_IO 的Protocol .目前看起来系统中只有一个这样的 Protocol ,所以我们用 LocateProtocol 就足够了。取得之后,通过这个 Protocol,对 eMMC 发送command。这个做法和 ATA 设备的 PassThrough Protocol很像。下面的代码是取得CID信息的,CID是用来识别eMMC一些基本信息的寄存器,比如 Serial Number,具体定义在 eMMC Specification中可以找到【参考1】 。
“8.2 CID register
The Card IDentification (CID) register is 128 bits wide. It contains the card identification information used during the card identification phase (MultiMediaCard protocol). Every individual flash or I/O card shall have an unique identification number. Every type of MultiMediaCard ROM cards (defined by content) shall have an unique identification number. Table 41 on page 112 lists these identifiers.The structure of the CID register is defined in the following sections”
image001

代码:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include <Library/MemoryAllocationLib.h>
#include  <Protocol/BlockIo.h>

#include "SDHostIo.h"
#include "mmc.h"


EFI_GUID gEfiSdHostIoProtocolGuid = { 0xb63f8ec7, 0xa9c9, 0x4472, 
					{0xa4, 0xc0, 0x4d, 0x8b, 0xf3, 0x65, 0xcc, 0x51}};
 
//
// Command timeout will be max 100 ms 
//
#define  TIMEOUT_COMMAND     100

extern EFI_BOOT_SERVICES         *gBS;

EFI_STATUS
SendCommand (
  IN   EFI_SD_HOST_IO_PROTOCOL    *This,
  IN   UINT16                     CommandIndex,
  IN   UINT32                     Argument,
  IN   TRANSFER_TYPE              DataType,
  IN   UINT8                      *Buffer, OPTIONAL
  IN   UINT32                     BufferSize,    
  IN   RESPONSE_TYPE              ResponseType,
  IN   UINT32                     TimeOut,  
  OUT  UINT32                     *ResponseData
  )
/*++

  Routine Description:
    Send command by using Host IO protocol
  
  Arguments:
    This           - Pointer to EFI_SD_HOST_IO_PROTOCOL
    CommandIndex   - The command index to set the command index field of command register
    Argument       - Command argument to set the argument field of command register
    DataType       - TRANSFER_TYPE, indicates no data, data in or data out
    Buffer         - Contains the data read from / write to the device
    BufferSize     - The size of the buffer
    ResponseType   - RESPONSE_TYPE
    TimeOut        - Time out value in 1 ms unit
    ResponseData   - Depending on the ResponseType, such as CSD or card status

  Returns:  
    EFI_INVALID_PARAMETER
    EFI_UNSUPPORTED
    EFI_DEVICE_ERROR
    EFI_SUCCESS
 --*/   
{

  EFI_STATUS    Status;

  Status = This->SendCommand (
           This,
           CommandIndex,
           Argument,
           DataType,
           Buffer,
           BufferSize,
           ResponseType,
           TimeOut,
           ResponseData
           );
  if (!EFI_ERROR (Status)) {
    if (ResponseType == ResponseR1 || ResponseType == ResponseR1b) {
      //ASSERT(ResponseData != NULL);
	  Print(L"Error with code [%d]",(*ResponseData));
    }
  } else {
    This->ResetSdHost (This, Reset_DAT_CMD);
  }

  return Status;
}

int
EFIAPI
main (
  IN UINT32 Argc,
  IN CHAR16 **Argv
  )
{
	EFI_SD_HOST_IO_PROTOCOL   *SdHostIo;
	EFI_STATUS 		Status;	
	CID             CIDReg;
    OCR             OCRReg;
	UINT32          TimeOut=5000;
	
	Status = gBS->LocateProtocol (       
                  &gEfiSdHostIoProtocolGuid,
                  NULL,
                  &SdHostIo
                  );
	if (EFI_ERROR (Status)) {
		Print(L"No SdHost driver, Application is exiting!\n");
		return Status;
	}  	

    SdHostIo->EnableAutoStopCmd (SdHostIo, TRUE);
    SdHostIo->SetupDevice (SdHostIo);

    //
    // Go to Idle
    //
	SendCommand (
                SdHostIo,
                GO_IDLE_STATE,
                0,
                NoData,
                NULL,
                0,  
                ResponseNo,
                TIMEOUT_COMMAND,
                NULL
            );

    gBS->Stall (100 * 1000);

    //
    // Check voltage support, first time we use 0x40FF8080
    //
    SendCommand (
                SdHostIo,
				SEND_OP_COND,
                0x40FF8080,
                NoData,
                NULL,
                0,  
                ResponseR3,
                TIMEOUT_COMMAND,
                (UINT32*)&(OCRReg)
            );

    while (OCRReg.Busy != 1) {
		OCRReg.AccessMode = 0x02; // sector mode;
		SendCommand (
				  SdHostIo,
                  SEND_OP_COND,
                  *(UINT32*)&(OCRReg),
                  NoData,
                  NULL,
                  0,  
                  ResponseR3,
                  TIMEOUT_COMMAND,
                  (UINT32*)&(OCRReg)
                  );
						  
        gBS->Stall(100);

        TimeOut--;
        if (TimeOut == 0) {
            Print(L"Card is always busy\n");
            Status = EFI_TIMEOUT;
            goto Exit;
        } 
    } 

	SendCommand(
          SdHostIo,
          ALL_SEND_CID,
		0,
          NoData,
          NULL,
          0,  
          ResponseR2,
          TIMEOUT_COMMAND,
          (UINT32*)&(CIDReg)
          );
  
    Print (L" Product serial number      : %X\n",CIDReg.PSN);
    Print (L" Product revision           : %X\n",CIDReg.PRV);	
    Print (L" Product name               : %c%c%c%c%c%c\n",
								CIDReg.PNM[0],
								CIDReg.PNM[1],
								CIDReg.PNM[2],
								CIDReg.PNM[3],
								CIDReg.PNM[4],
								CIDReg.PNM[5]);
    Print (L" Manufacturer ID            : %X\n",CIDReg.MID);	
	
Exit:	
	return EFI_SUCCESS;
}

在kabyLake HDK 上运行结果:
image002

完整代码下载,其中还有代码生成的EFI程序,是 X64的。
getemmc

本文使用的头文件都是来自新版的 EDK2(比 UDK2015要新一些,我觉得UDK2017有可能会正式加入吧)【参考2】,有兴趣的读者可以自行查阅。
最后,既然有了CID,那么还可以读取一些关于 eMMC的其他信息,比如容量。请读者自己尝试完成。
最后的最后,推荐 Lenovo 出品的一款擦除 eMMC的工具,在 Shell 下运行,可以很快擦掉全部内容(应该使用 eMMC 的 Command 直接Erase的,所以能够达到很快的速度)。
gufd01ww

参考:
1. http://www.jedec.org/standards-documents/results/jesd84-b51 JESD84-A44.pdf
2. https://github.com/tianocore/edk2
3. http://support.lenovo.com/us/en/downloads/ds100934

Step to UEFI (104)eMMC in the Shell》上有 8 条评论

  1. ch

    作者你好,我看了你的uefi获取MMC信息的代码,我现在遇到个问题,就是在我执行你这段代码以后,再使用BlockIo读取硬盘会失败,这是什么原因

    回复
    1. ziv2013 文章作者

      不好意思,最近忙刚看到。你是获取emmc的失败还是获取其他硬盘的操作失败?回头我试试。

      回复
      1. ch

        我是获取eMMC信息成功了,但之后我想调用BlockIo进行读取硬盘操作却失败了,如果没有执行过这个程序,则用BlockIo读硬盘就没问题,一旦执行过这个程序,除非重启,否则BlockIo读硬盘一定失败

        回复
      2. ch

        而且我只进行EnableAutoStopCmd 这个操作,SendCommand不执行,BlockIo读取硬盘也会失败,而且我用的是OpenProtocol打开SdHostIo,再用CloseProtocol关闭,这样也不行,很奇怪

        回复
          1. ch

            不论是初始化还是reset我都试了,我想问作者你先调用sdhostIo获取eMMC信息再调用BlockIo读硬盘可以读取成功吗

发表评论

电子邮件地址不会被公开。 必填项已用*标注