2015年1月20日 星期二

HID over I2C Device

如果一個I2C Device是相容於HID規範的話,從Register 0位置便可讀取出HID Descriptor。
這邊有個範例,讀出來的資料如下:
1E 00 00 01 A0 03 02 00 03 00 2A 00 00 00 00 00
04 00 05 00 AA AA BB BB 11 00 00 00 00 00

在此可對應到此表格:
Byte OffsetFieldSize(Bytes)Example
0wHIDDescLength21E
2bcdVersion201
4wReportDescLength23A0
6wReportDescRegister202
8wInputRegister203
10wMaxInputLength22A
12wOutputRegister20
14wMaxOutputLength20
16wCommandRegister204
18wDataRegister205
20wVendorID2AAAA
22wProductID2BBBB
24wVersionID211
26RESERVED40

此Table為Device的Information,除了最基本的VID、DID外,還有各個Register的Index指出Data該從哪邊讀與該從哪邊寫。
而wReportDescRegister是Report Descriptor的Index,從此Index將會讀取出一大串的Data,可參考wReportDescLength先配置好存放這些Data的記憶體空間。
關於一個Device的所支援的各種Type都會在這個Descriptor裡面,而一個Descriptor可能會有好幾組Usage Page與Report ID。
例如一個Touch Panel可能會支援Finger與Pen,而這兩個所使用的Report ID是不同的,所以需要去解析每個Report,去取得所要的資料。

Report Descriptor

這邊將探討如何去解析Report Descriptor。
Report Descriptor裡面是由一個個Item所組成的,可分為Short Items與Long Items,通常使用Long Items的情況為,需要存放額外的大量資料時才使用Long Items,否則一般都是用Short Items,長度為1到5 bytes。

可以先判斷一個Item是Short Item或Long Item:
Short Items:
 

先看第一個Byte (Offset 0),可以分成三個部分並對應到下表:
bSizeNumeric expression specifying size of data
00 bytes
11 bytes
22 bytes
34 bytes

bTypeNumeric expression identifying type of item where
0Main
1Global
2Local
3Reserved

bTagNumeric expression specifying the function of the item
[data]Optional data

Data長度可參考bSize,對Short Item來說最多就是4個Bytes。

Long Items:


如果第一個Byte (Offset 0)是0xFE,那這個便是Long Item,而第二個Byte就用來表示整個Data Size,第三個Byte為其Tag,而後面就是整個Data部分。

在此簡單舉一個例子:
05 0D 09 04 A1 01 85 0C 95 01 75 08 26 FF 00 15
00 81 03
09 54 81 02 05 0D 09 22 A1 02 09 42 15
00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95
05 81 03 75 10 09 51 95 01 81 02 05 01 75 10 95
01 55 0E 65 11 09 30 26 0C 28 35 00 46 03 0A 81
02 46 A2 05 09 31 26 88 16 81 02 C0 05 0D 09 22

這是一部分的Report Descriptor,從第一個Byte開始。
05: 00000101b Tag: 0000b Type: 01b Size: 01b
Type: 01b (Global Items)
Tag: 0000b (Usage Page)
Data: 0D (Digitizer)

09: 00001001b Tag: 0000b Type: 10b Size: 01b
Type: 10b (Local Items)
Tag: 0000b (Usage)
Data: 04 (Touch Screen)

A1: 10100001b Tag: 1010b Type: 00b Size: 01b
Type: 00b (Main Items)
Tag: 1010b (Collection)
Data: 01 (Application)

85: 10000101b Tag: 1000b Type: 01b Size: 01b
Type: 01b (Global Items)
Tag: 1000b (Report ID)
Data: 0C

95: 10010101b Tag: 1001b Type: 01b Size: 01b
Type: 01b (Global Items)
Tag: 1001b (Report Count)
Data: 01

75: 01110101b Tag: 0111b Type: 01b Size: 01b
Type: 01b (Global Items)
Tag: 0111b (Report Size)
Data: 08

26: 00100110b Tag: 0010b Type: 01b Size: 10b
Type: 01b (Global Items)
Tag: 0010b (Logical Maximum)
Data: 00FF

15: 00010101b Tag: 0010b Type: 01b Size: 01b
Type: 01b (Global Items)
Tag: 0010b (Logical Minimum)
Data: 00

81: 10000001b Tag: 1000b Type: 00b Size: 01b
Type: 00b (Main Items)
Tag: 1000b (Input)
Data: 03

以此類推,可以看到每個Item就是這樣串起來的。
解析整個Report Descriptor並不難,但需要一直對照Spec,可參考HID1_11.pdf。

Input Register


Input Register可從wInputRegister這個位置讀出來,但還是必須先做一些前置處理,否則會不知道它的Layout(每個資料所在的Offset,如X、Y軸)。
這部分的資訊可從Report Descriptor取得。
Example:(這不是一個完整的Report Descriptor)
Usage Page (Digitizer)
Usage (Touch Screen)
Collection (Application)
  Report ID (0x0C)
  Report Count (1)
  Report Size (8)

  Input (0x03)
  Usage (Reserved)
  Input (0x02)
  Usage Page (Digitizer)
  Usage (Finger)
  Collection (Logical)
    Usage (Tip Switch)
    Report Size (1)
    Report Count (1)

    Input (0x02)
    Input (0x03)
    Usage (Reserved)
    Input (0x02)
    Report Count (5)
    Input (0x03)
    Report Size (16)
    Usage (Reserved)
    Report Count (1)
    Input (0x02)
    Usage Page (Generic Desktop Controls)
    Report Size (16)
    Report Count (1)

    Usage (X)
    Input (0x02)
    Usage (Y)
    Input (0x02)
  End Collection ()

上面共有9個Input,首先可以注意在Input前面有Report Count與Report Size,每看到一個新的Report Count或Report Size,就以新的值來取代。
所以上面的例子:
Input 1st Length: 8 bits
Input 2nd Length: 8 bits
Input 3rd Length: 1 bits
Input 4th Length: 1 bits
Input 5th Length: 1 bits
Input 6th Length: 5 bits
Input 7th Length: 16 bits
Input 8th Length: 16 bits
Input 9th Length: 16 bits

而在第8個Input前的Usage (X)是Usage Page (Generic Desktop Controls)的X座標值,第9個Input前的Usage (Y)為Y的座標值。

以下為從Input Register讀出來的資料:
2A 00 0C 00 01 05 01 00 81 00 2B 01 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 1C A9

前面兩個Byte為整個資料的長度:2A (42個Bytes)
0C為對應的Report ID,代表讀回來的Register是對應到這個Report。
前面有提到一個Panel可能有支援Finger與Pen等。所以從Device讀出來的Report Descriptor會有好幾組Usage Page,每一組可能都會有對應的Report ID。
所以讀回來的Input Register會處理前,要先比對一下是不是相同的ID,不是的話就再換下一個Usage Page。

而在這3個Bytes後面就是實際上要使用的Data,可以從剛剛上面的Input,來推算出對應的Usage的Offset。
Usage (Tip Switch)的值: 05 (bit 0)
Usage (X)的值: 0081
Usage (Y)的值: 012B

2014年10月22日 星期三

PCD (Platform Configuration Database) - Part 3 (DXE)

將接續上一篇,繼續探討DXE部分。

AutoGen.h
typedef struct {
  UINT32             PcdSetupConOutColumn_d3705011_bc19_4af7_be16_f68030378c15_VariableDefault_0;
  UINT32             PcdSetupConOutRow_d3705011_bc19_4af7_be16_f68030378c15_VariableDefault_0;

  DYNAMICEX_MAPPING  ExMapTable[DXE_EXMAPPING_TABLE_SIZE];
  UINT32             LocalTokenNumberTable[DXE_LOCAL_TOKEN_NUMBER_TABLE_SIZE];
  GUID               GuidTable[DXE_GUID_TABLE_SIZE];
  STRING_HEAD        PcdWinNtMemorySize_0d79a645_1d91_40a6_a81f_61e6982b32b4[1];
  STRING_HEAD        PcdWinNtPhysicalDisk_0d79a645_1d91_40a6_a81f_61e6982b32b4[1];
  STRING_HEAD        PcdWinNtVirtualDisk_0d79a645_1d91_40a6_a81f_61e6982b32b4[1];
  STRING_HEAD        PcdWinNtFileSystem_0d79a645_1d91_40a6_a81f_61e6982b32b4[1];
  STRING_HEAD        PcdWinNtSerialPort_0d79a645_1d91_40a6_a81f_61e6982b32b4[1];
  STRING_HEAD        PcdWinNtGop_0d79a645_1d91_40a6_a81f_61e6982b32b4[1];
  STRING_HEAD        PcdWinNtUga_0d79a645_1d91_40a6_a81f_61e6982b32b4[1];
  STRING_HEAD        PcdWinNtConsole_0d79a645_1d91_40a6_a81f_61e6982b32b4[1];

  VARIABLE_HEAD      PcdHardwareErrorRecordLevel_d3705011_bc19_4af7_be16_f68030378c15_Variable_Header[1];
  VARIABLE_HEAD      PcdPlatformBootTimeOut_d3705011_bc19_4af7_be16_f68030378c15_Variable_Header[1];
  VARIABLE_HEAD      PcdSetupConOutColumn_d3705011_bc19_4af7_be16_f68030378c15_Variable_Header[1];
  VARIABLE_HEAD      PcdSetupConOutRow_d3705011_bc19_4af7_be16_f68030378c15_Variable_Header[1];

  UINT8              StringTable[12]; /* PcdWinNtMemorySize_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  UINT8              StringTable_1[100]; /* PcdWinNtPhysicalDisk_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  UINT8              StringTable_2[26]; /* PcdWinNtVirtualDisk_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  UINT8              StringTable_3[106]; /* PcdWinNtFileSystem_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  UINT8              StringTable_4[20]; /* PcdWinNtSerialPort_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  UINT8              StringTable_5[52]; /* PcdWinNtGop_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  UINT8              StringTable_6[52]; /* PcdWinNtUga_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  UINT8              StringTable_7[52]; /* PcdWinNtConsole_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  UINT8              StringTable_8[32]; /* PcdHardwareErrorRecordLevel_d3705011_bc19_4af7_be16_f68030378c15 */
  UINT8              StringTable_9[16]; /* PcdPlatformBootTimeOut_d3705011_bc19_4af7_be16_f68030378c15 */
  UINT8              StringTable_10[38]; /* PcdSetupConOutColumn_d3705011_bc19_4af7_be16_f68030378c15 */

  SIZE_INFO          SizeTable[DXE_SIZE_TABLE_SIZE];

  UINT16             PcdHardwareErrorRecordLevel_d3705011_bc19_4af7_be16_f68030378c15_VariableDefault_0;
  UINT16             PcdPlatformBootTimeOut_d3705011_bc19_4af7_be16_f68030378c15_VariableDefault_0;

  BOOLEAN            PcdBootState_d3705011_bc19_4af7_be16_f68030378c15[1];

  UINT8              SkuIdTable[DXE_SKUID_TABLE_SIZE];

} DXE_PCD_DATABASE_INIT;

typedef struct {
  UINT8  dummy; /* PCD_DATABASE_UNINIT is emptry */
} DXE_PCD_DATABASE_UNINIT;

typedef struct {
  DXE_PCD_DATABASE_INIT    Init;
  DXE_PCD_DATABASE_UNINIT  Uninit;
} DXE_PCD_DATABASE;

typedef struct {
  PEI_PCD_DATABASE PeiDb;
  DXE_PCD_DATABASE DxeDb;
} PCD_DATABASE;

AutoGen.c
DXE_PCD_DATABASE_INIT gDXEPcdDbInit = {
  80U, /* PcdSetupConOutColumn_d3705011_bc19_4af7_be16_f68030378c15_VariableDefault_0 */
  25U, /* PcdSetupConOutRow_d3705011_bc19_4af7_be16_f68030378c15_VariableDefault_0 */

  /* VPD */

  /* ExMapTable */
  {
    { 0U, 0U, 0U },
  },
  /* LocalTokenNumberTable */
  {
    offsetof(DXE_PCD_DATABASE, Init.PcdWinNtMemorySize_0d79a645_1d91_40a6_a81f_61e6982b32b4) | PCD_TYPE_STRING,
    offsetof(DXE_PCD_DATABASE, Init.PcdWinNtPhysicalDisk_0d79a645_1d91_40a6_a81f_61e6982b32b4) | PCD_TYPE_STRING,
    offsetof(DXE_PCD_DATABASE, Init.PcdWinNtVirtualDisk_0d79a645_1d91_40a6_a81f_61e6982b32b4) | PCD_TYPE_STRING,
    offsetof(DXE_PCD_DATABASE, Init.PcdWinNtFileSystem_0d79a645_1d91_40a6_a81f_61e6982b32b4) | PCD_TYPE_STRING,
    offsetof(DXE_PCD_DATABASE, Init.PcdWinNtSerialPort_0d79a645_1d91_40a6_a81f_61e6982b32b4) | PCD_TYPE_STRING,
    offsetof(DXE_PCD_DATABASE, Init.PcdWinNtGop_0d79a645_1d91_40a6_a81f_61e6982b32b4) | PCD_TYPE_STRING,
    offsetof(DXE_PCD_DATABASE, Init.PcdWinNtUga_0d79a645_1d91_40a6_a81f_61e6982b32b4) | PCD_TYPE_STRING,
    offsetof(DXE_PCD_DATABASE, Init.PcdWinNtConsole_0d79a645_1d91_40a6_a81f_61e6982b32b4) | PCD_TYPE_STRING,
    offsetof(DXE_PCD_DATABASE, Init.PcdHardwareErrorRecordLevel_d3705011_bc19_4af7_be16_f68030378c15_Variable_Header) | PCD_TYPE_HII | PCD_DATUM_TYPE_UINT16,
    offsetof(DXE_PCD_DATABASE, Init.PcdPlatformBootTimeOut_d3705011_bc19_4af7_be16_f68030378c15_Variable_Header) | PCD_TYPE_HII | PCD_DATUM_TYPE_UINT16,
    offsetof(DXE_PCD_DATABASE, Init.PcdSetupConOutColumn_d3705011_bc19_4af7_be16_f68030378c15_Variable_Header) | PCD_DATUM_TYPE_UINT32 | PCD_TYPE_HII,
    offsetof(DXE_PCD_DATABASE, Init.PcdSetupConOutRow_d3705011_bc19_4af7_be16_f68030378c15_Variable_Header) | PCD_DATUM_TYPE_UINT32 | PCD_TYPE_HII,
    offsetof(DXE_PCD_DATABASE, Init.PcdBootState_d3705011_bc19_4af7_be16_f68030378c15) | PCD_DATUM_TYPE_UINT8 | PCD_TYPE_DATA,
  },
  /* GuidTable */
  {
    { 0x8BE4DF61, 0x93CA, 0x11D2, { 0xAA, 0x0D, 0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C }},
  },
  { 0U }, /* PcdWinNtMemorySize_0d79a645_1d91_40a6_a81f_61e6982b32b4[1] */
  { 12U }, /* PcdWinNtPhysicalDisk_0d79a645_1d91_40a6_a81f_61e6982b32b4[1] */
  { 112U }, /* PcdWinNtVirtualDisk_0d79a645_1d91_40a6_a81f_61e6982b32b4[1] */
  { 138U }, /* PcdWinNtFileSystem_0d79a645_1d91_40a6_a81f_61e6982b32b4[1] */
  { 244U }, /* PcdWinNtSerialPort_0d79a645_1d91_40a6_a81f_61e6982b32b4[1] */
  { 264U }, /* PcdWinNtGop_0d79a645_1d91_40a6_a81f_61e6982b32b4[1] */
  { 316U }, /* PcdWinNtUga_0d79a645_1d91_40a6_a81f_61e6982b32b4[1] */
  { 368U }, /* PcdWinNtConsole_0d79a645_1d91_40a6_a81f_61e6982b32b4[1] */

  /* PcdHardwareErrorRecordLevel_d3705011_bc19_4af7_be16_f68030378c15_Variable_Header[1] */
  {
    { 420U, offsetof(DXE_PCD_DATABASE, Init.PcdHardwareErrorRecordLevel_d3705011_bc19_4af7_be16_f68030378c15_VariableDefault_0), 0U, 0x0U }
  },
  /* PcdPlatformBootTimeOut_d3705011_bc19_4af7_be16_f68030378c15_Variable_Header[1] */
  {
    { 452U, offsetof(DXE_PCD_DATABASE, Init.PcdPlatformBootTimeOut_d3705011_bc19_4af7_be16_f68030378c15_VariableDefault_0), 0U, 0x0U }
  },
  /* PcdSetupConOutColumn_d3705011_bc19_4af7_be16_f68030378c15_Variable_Header[1] */
  {
    { 468U, offsetof(DXE_PCD_DATABASE, Init.PcdSetupConOutColumn_d3705011_bc19_4af7_be16_f68030378c15_VariableDefault_0), 0U, 0x0U }
  },
  /* PcdSetupConOutRow_d3705011_bc19_4af7_be16_f68030378c15_Variable_Header[1] */
  {
    { 468U, offsetof(DXE_PCD_DATABASE, Init.PcdSetupConOutRow_d3705011_bc19_4af7_be16_f68030378c15_VariableDefault_0), 0U, 0x4U }
  },

 /* StringTable */
  {0x36, 0x00, 0x34, 0x00, 0x21, 0x00, 0x36, 0x00, 0x34, 0x00, 0x00, 0x00}, /* PcdWinNtMemorySize_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  {0x61, 0x00, 0x3a, 0x00, 0x52, 0x00, 0x57, 0x00, 0x3b, 0x00, 0x32, 0x00, 0x38, 0x00, 0x38, 0x00, 0x30, 0x00, 0x3b, 0x00, 0x35, 0x00, 0x31, 0x00, 0x32, 0x00, 0x21, 0x00, 0x64, 0x00, 0x3a, 0x00, 0x52, 0x00, 0x4f, 0x00, 0x3b, 0x00, 0x33, 0x00, 0x30, 0x00, 0x37, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x3b, 0x00, 0x32, 0x00, 0x30, 0x00, 0x34, 0x00, 0x38, 0x00, 0x21, 0x00, 0x6a, 0x00, 0x3a, 0x00, 0x52, 0x00, 0x57, 0x00, 0x3b, 0x00, 0x32, 0x00, 0x36, 0x00, 0x32, 0x00, 0x31, 0x00, 0x34, 0x00, 0x34, 0x00, 0x3b, 0x00, 0x35, 0x00, 0x31, 0x00, 0x32, 0x00, 0x00, 0x00}, /* PcdWinNtPhysicalDisk_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  {0x46, 0x00, 0x57, 0x00, 0x3b, 0x00, 0x34, 0x00, 0x30, 0x00, 0x39, 0x00, 0x36, 0x00, 0x30, 0x00, 0x3b, 0x00, 0x35, 0x00, 0x31, 0x00, 0x32, 0x00, 0x00, 0x00}, /* PcdWinNtVirtualDisk_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  {0x2e, 0x00, 0x21, 0x00, 0x2e, 0x00, 0x2e, 0x00, 0x5c, 0x00, 0x2e, 0x00, 0x2e, 0x00, 0x5c, 0x00, 0x2e, 0x00, 0x2e, 0x00, 0x5c, 0x00, 0x55, 0x00, 0x64, 0x00, 0x75, 0x00, 0x50, 0x00, 0x6b, 0x00, 0x67, 0x00, 0x5c, 0x00, 0x44, 0x00, 0x45, 0x00, 0x42, 0x00, 0x55, 0x00, 0x47, 0x00, 0x5f, 0x00, 0x56, 0x00, 0x53, 0x00, 0x32, 0x00, 0x30, 0x00, 0x31, 0x00, 0x32, 0x00, 0x78, 0x00, 0x38, 0x00, 0x36, 0x00, 0x5c, 0x00, 0x49, 0x00, 0x41, 0x00, 0x33, 0x00, 0x32, 0x00, 0x00, 0x00}, /* PcdWinNtFileSystem_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  {0x43, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x31, 0x00, 0x21, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x32, 0x00, 0x00, 0x00}, /* PcdWinNtSerialPort_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  {0x55, 0x00, 0x47, 0x00, 0x41, 0x00, 0x20, 0x00, 0x57, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x20, 0x00, 0x31, 0x00, 0x00, 0x00}, /* PcdWinNtGop_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  {0x55, 0x00, 0x47, 0x00, 0x41, 0x00, 0x20, 0x00, 0x57, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x20, 0x00, 0x31, 0x00, 0x00, 0x00}, /* PcdWinNtUga_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  {0x42, 0x00, 0x75, 0x00, 0x73, 0x00, 0x20, 0x00, 0x44, 0x00, 0x72, 0x00, 0x69, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x57, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x00, 0x00}, /* PcdWinNtConsole_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  {0x48, 0x00, 0x77, 0x00, 0x45, 0x00, 0x72, 0x00, 0x72, 0x00, 0x52, 0x00, 0x65, 0x00, 0x63, 0x00, 0x53, 0x00, 0x75, 0x00, 0x70, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x74, 0x00, 0x00, 0x00}, /* PcdHardwareErrorRecordLevel_d3705011_bc19_4af7_be16_f68030378c15 */
  {0x54, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00}, /* PcdPlatformBootTimeOut_d3705011_bc19_4af7_be16_f68030378c15 */
  {0x53, 0x00, 0x65, 0x00, 0x74, 0x00, 0x75, 0x00, 0x70, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x66, 0x00, 0x69, 0x00, 0x67, 0x00, 0x00, 0x00}, /* PcdSetupConOutColumn_d3705011_bc19_4af7_be16_f68030378c15 */

  /* SizeTable */
  {
    12U, 12U, /* PcdWinNtMemorySize_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
    100U, 94U, /* PcdWinNtPhysicalDisk_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
    26U, 26U, /* PcdWinNtVirtualDisk_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
    106U, 78U, /* PcdWinNtFileSystem_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
    20U, 20U, /* PcdWinNtSerialPort_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
    52U, 26U, /* PcdWinNtGop_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
    52U, 26U, /* PcdWinNtUga_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
    52U, 52U, /* PcdWinNtConsole_0d79a645_1d91_40a6_a81f_61e6982b32b4 */
  },

  1U, /* PcdHardwareErrorRecordLevel_d3705011_bc19_4af7_be16_f68030378c15_VariableDefault_0 */
  10U, /* PcdPlatformBootTimeOut_d3705011_bc19_4af7_be16_f68030378c15_VariableDefault_0 */

  { 1U }, /*  PcdBootState_d3705011_bc19_4af7_be16_f68030378c15[1] */

  /* SkuIdTable */
  { 0U,  }, 
};

以上是在Compile過程中,Build Tool為PCD DXE所生成的檔案,跟PEIM的很相似,但多了一些沒看過的PCD。。
上面有個PCD_DATABASE,裡面分了PeiDb與DxeDb,在一開始的EntryPoint中會呼叫BuildPcdDxeDataBase,並把PEI_PCD_DATABASE從HOB複製過來。

PCD DXE的GetWorkerSetWorker

GetWorker:


VOID *
GetWorker (
  IN UINTN               TokenNumber,
  IN UINTN               GetSize
  )

在一開始會做一些前置處理,判斷傳進來的TokenNumber判斷是PEI的PCD還是DXE的PCD。
然後把對應的LocalTokenNumber、LocalTokenNumber、StringTable、PcdDb的Pointer另取出來。
IsPeiDb = (BOOLEAN) ((TokenNumber + 1 < PEI_LOCAL_TOKEN_NUMBER + 1) ? TRUE : FALSE);

透過Mask單獨取得Offset。
Offset = LocalTokenNumber & PCD_DATABASE_OFFSET_MASK;

接下來會依照各種不同PCD Type做不同的處理:
PCD_TYPE_VPD:
傳回VPD (Vital Product Data)中特定的Offset的資料。
VPD_HEAD *VpdHead;
VpdHead = (VPD_HEAD *) ((UINT8 *)PeiPcdDb + Offset);
return (VOID *) (UINTN) (PcdGet32 (PcdVpdBaseAddress) + VpdHead->Offset);


PCD_TYPE_HII|PCD_TYPE_STRING:
PCD_TYPE_HII:
透過Offset取得Variable Head,裡面有著GUID與Name的Index。
VariableHead = (VARIABLE_HEAD *) ((UINT8 *)PeiPcdDb + Offset);

Guid = &(PeiPcdDb->Init.GuidTable[VariableHead->GuidTableIndex]);
Name = (UINT16*)&StringTable[VariableHead->StringIndex];


會先判斷是否為PCD_TYPE_HII|PCD_TYPE_STRING或PCD_TYPE_HII,再去做不同的處理,差別只是在於在GetVariable失敗時,
PCD_TYPE_HII|PCD_TYPE_STRING會於StringTable取得預設的資料(字串),而PCD_TYPE_HII會直接以DefaultValueOffset去取得對應的預設值。
if ((LocalTokenNumber & PCD_TYPE_ALL_SET) == (PCD_TYPE_HII|PCD_TYPE_STRING)) {
  StringTableIdx = *(STRING_HEAD*)((UINT8 *) PcdDb + VariableHead->DefaultValueOffset);  
  VaraiableDefaultBuffer = (VOID *) (StringTable + StringTableIdx);
  ...
} else {
  VaraiableDefaultBuffer = (UINT8 *) PcdDb + VariableHead->DefaultValueOffset;
  ...
}


PCD_TYPE_STRING:
先取得該PCD的STRING_HEAD (StringTableIdx (UINT32)),裡面存放著對應StringTable的Offset。
StringTableIdx = * (STRING_HEAD*) ((UINT8 *) PeiPcdDb + Offset);
return (VOID *) (&StringTable[StringTableIdx]);


PCD_TYPE_DATA:
直接透過Offset取得Data。
return (VOID *) ((UINT8 *)PeiPcdDb + Offset);

SetWorker:


EFI_STATUS
SetWorker (
  IN          UINTN               TokenNumber,
  IN          VOID                *Data,
  IN OUT      UINTN               *Size,
  IN          BOOLEAN             PtrType
  )

透過Token Number取得該PCD的LocalTokenNumber (Offset、PCD Type、Data Type)。
LocalTokenNumber = PeiPcdDb->Init.LocalTokenNumberTable[TokenNumber];

會先判斷Data Type是不是Pointer,通常像String之類的,會被歸類為Pointer。
要注意到在Build Tool會在PCD Database中,先宣告好固定長度的陣列來存放String等其它Data。
如果PtrType為True,而傳入的Size又大於原先預留好的最大值的話,就會造成問題,這邊就先擋掉並回傳MaxSize。
if (PtrType) {
  //
  // Get MaxSize first, then check new size with max buffer size.
  //
  GetPtrTypeSize (TokenNumber, &MaxSize, PeiPcdDb);
  if (*Size > MaxSize) {
    *Size = MaxSize;
    return EFI_INVALID_PARAMETER;
  }
} else {
  if (*Size != PeiPcdGetSize (TokenNumber + 1)) {
    return EFI_INVALID_PARAMETER;
  }
}


當某個PCD被修改時會去呼叫,為該PCD註冊Notify的Function。
可以透過PCD Library的LibPcdCallbackOnSet來進行註冊會比較方便。
if ((TokenNumber + 1 < PEI_NEX_TOKEN_NUMBER + 1) || (TokenNumber + 1 >= PEI_LOCAL_TOKEN_NUMBER + 1 || TokenNumber + 1 < (PEI_LOCAL_TOKEN_NUMBER + DXE_NEX_TOKEN_NUMBER + 1))) {
  InvokeCallbackOnSet (0, NULL, TokenNumber + 1, Data, *Size);
}


跟GetWorker一樣會判斷傳進來的TokenNumber判斷是PEI的PCD還是DXE的PCD。
然後把對應的LocalTokenNumber、LocalTokenNumber、StringTable、PcdDb的Pointer另取出來。

透過Mask單獨取得Offset,並取得Data。
Offset = LocalTokenNumber & PCD_DATABASE_OFFSET_MASK;
InternalData    = (VOID *) ((UINT8 *) PeiPcdDb + Offset);


接下來會依照各種不同PCD Type做不同的處理:
PCD_TYPE_VPD:
可能是上述幾個Type都不支援在PEI修改NVS,所以這邊就直接return回EFI_INVALID_PARAMETER。

PCD_TYPE_STRING:
傳入的資料為一串Array,資料長度可能會變動,所以先更改Current Size。
SizeTable中有兩個欄位,一個是Max Size,一個是Current Size。
之後再透過CopyMem,將傳入的Data Copy到StringTable中。
if (SetPtrTypeSize (TokenNumber, Size, PeiPcdDb)) {
  CopyMem (StringTable + *((STRING_HEAD *)InternalData), Data, *Size);
  return EFI_SUCCESS;
} else {
  return EFI_INVALID_PARAMETER;
}


PCD_TYPE_HII:
PCD_TYPE_HII|PCD_TYPE_STRING:
透過SetVariable將資料存於NVS並更新PCD Database內的資料。
VariableHead = (VARIABLE_HEAD *) (PcdDb + Offset);
    
Guid = GuidTable + VariableHead->GuidTableIndex;
Name = (UINT16*) (StringTable + VariableHead->StringIndex);
VariableOffset = VariableHead->Offset;
Status = SetHiiVariable (Guid, Name, Data, *Size, VariableOffset);

if (EFI_NOT_FOUND == Status) {
  if ((LocalTokenNumber & PCD_TYPE_ALL_SET) == (PCD_TYPE_HII|PCD_TYPE_STRING))  {
    CopyMem (StringTable + *(STRING_HEAD *)(PcdDb + VariableHead->DefaultValueOffset), Data, *Size);
  } else {
    CopyMem (PcdDb + VariableHead->DefaultValueOffset, Data, *Size);
  }
  Status = EFI_SUCCESS;
}


PCD_TYPE_DATA:
PtrType的處理方式跟PCD_TYPE_STRING很像。
if (PtrType) {
  if (SetPtrTypeSize (TokenNumber, Size, PeiPcdDb)) {
    CopyMem (InternalData, Data, *Size);
    return EFI_SUCCESS;
  } else {
    return EFI_INVALID_PARAMETER;
  }
}


其餘基本型態只是透過轉型來處理。
switch (*Size) {
  case sizeof(UINT8):
    *((UINT8 *) InternalData) = *((UINT8 *) Data);
    return EFI_SUCCESS;

  case sizeof(UINT16):
    *((UINT16 *) InternalData) = *((UINT16 *) Data);
    return EFI_SUCCESS;

  case sizeof(UINT32):
    *((UINT32 *) InternalData) = *((UINT32 *) Data);
    return EFI_SUCCESS;

  case sizeof(UINT64):
    *((UINT64 *) InternalData) = *((UINT64 *) Data);
    return EFI_SUCCESS;

  default:
    ASSERT (FALSE);
    return EFI_NOT_FOUND;
}


2014年10月20日 星期一

PCD (Platform Configuration Database) - Part 2 (PEI)

這篇將探討PCD背後的原理,PEI與DXE都分別提供PPI與Protocol給予其它Driver使用,雖然一般都是透過PcdLib來存取Dynamic類型的PCD,但實際上PcdLib是使用PPI或Protocol來達成存取PCD的目的。

AutoGen.h
typedef struct {
  DYNAMICEX_MAPPING  ExMapTable[PEI_EXMAPPING_TABLE_SIZE];
  UINT32             LocalTokenNumberTable[PEI_LOCAL_TOKEN_NUMBER_TABLE_SIZE];
  GUID               GuidTable[PEI_GUID_TABLE_SIZE];

  UINT8              StringTable[1]; /* _ */

  SIZE_INFO          SizeTable[PEI_SIZE_TABLE_SIZE];

  UINT8              SkuIdTable[PEI_SKUID_TABLE_SIZE];
  SKU_ID             SystemSkuId;
} PEI_PCD_DATABASE_INIT;

typedef struct {
  UINT64   PcdS3BootScriptTablePrivateDataPtr_a1aff049_fdeb_442a_b320_13ab4cb72bbc[1];

  UINT32   PcdFlashNvStorageFtwWorkingBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc[1];
  UINT32   PcdFlashNvStorageFtwSpareBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc[1];
  UINT32   PcdFlashNvStorageVariableBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc[1];
} PEI_PCD_DATABASE_UNINIT;

typedef struct {
  PEI_PCD_DATABASE_INIT    Init;
  PEI_PCD_DATABASE_UNINIT  Uninit;
} PEI_PCD_DATABASE;

AutoGen.c
PEI_PCD_DATABASE_INIT gPEIPcdDbInit = {
  /* VPD */

  /* ExMapTable */
  {
    { 0U, 0U, 0U },
  },
  /* LocalTokenNumberTable */
  {
    offsetof(PEI_PCD_DATABASE, Uninit.PcdFlashNvStorageFtwWorkingBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_TYPE_DATA | PCD_DATUM_TYPE_UINT32,
    offsetof(PEI_PCD_DATABASE, Uninit.PcdFlashNvStorageFtwSpareBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_TYPE_DATA | PCD_DATUM_TYPE_UINT32,
    offsetof(PEI_PCD_DATABASE, Uninit.PcdFlashNvStorageVariableBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_TYPE_DATA | PCD_DATUM_TYPE_UINT32,
    offsetof(PEI_PCD_DATABASE, Uninit.PcdS3BootScriptTablePrivateDataPtr_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_DATUM_TYPE_UINT64 | PCD_TYPE_DATA,
  },
  /* GuidTable */
  {
    {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
  },
 /* StringTable */
  { 0 }, /* _ */

  /* SizeTable */
  {
    0U, 0U, /* _ */
  },
  /* SkuIdTable */
  { 0U,  },
  0U
};

以上是在Compile過程中,Build Tool為PCD PEIM所生成的檔案。
可以注意到這邊只有少數幾組PCD,原因是PCD PEIM中不會包含只在DXE被使用的PCD。
而這個PEI_PCD_DATABASE,最後會透過HOB傳給PCD DXE。
建立HOB的過程可參考BuildPcdDatabase

整個Structure分為InitUninit的部分,兩個差別是在於,如果有定義好預設值的Dynamic PCD,其預設值會被放在Init裡面,但如果在DSC裡定義的值為0或NULL的話,會被視為無預設值,且會放置於Uninit中。

  /* LocalTokenNumberTable */
  {
    offsetof(PEI_PCD_DATABASE, Uninit.PcdFlashNvStorageFtwWorkingBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_TYPE_DATA | PCD_DATUM_TYPE_UINT32,
    offsetof(PEI_PCD_DATABASE, Uninit.PcdFlashNvStorageFtwSpareBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_TYPE_DATA | PCD_DATUM_TYPE_UINT32,
    offsetof(PEI_PCD_DATABASE, Uninit.PcdFlashNvStorageVariableBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_TYPE_DATA | PCD_DATUM_TYPE_UINT32,
    offsetof(PEI_PCD_DATABASE, Uninit.PcdS3BootScriptTablePrivateDataPtr_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_DATUM_TYPE_UINT64 | PCD_TYPE_DATA,
  },

PEI_PCD_DATABASE_INIT中的LocalTokenNumberTable會在Compile過程中,會決定好每個PCD中的Data在PEI_PCD_DATABASE中的Offset以及Data Type與PCD Type。

#define offsetof(s,m)  (UINT32) (UINTN) &(((s *)0)->m)
代入一個Structure的原型與其Member,會算出其Member的Offset為多少。

PCD Type Mask:
PCD_TYPE_DATA
PCD_TYPE_HII
PCD_TYPE_VPD
PCD_TYPE_SKU_ENABLED
PCD_TYPE_STRING

Datum Type:
PCD_DATUM_TYPE_POINTER
PCD_DATUM_TYPE_UINT8
PCD_DATUM_TYPE_UINT16
PCD_DATUM_TYPE_UINT32
PCD_DATUM_TYPE_UINT64

在上面的LocalTokenNumberTable中,共有四個PCD,如果去搜尋整套編譯過的Source Code,可以在某些AutoGen.h看到這些Dynamic PCD的Token Number,並且可以注意到第一個PCD的Token為1,那為何不是從0開始?0是給PCD_INVALID_TOKEN_NUMBER使用(Source Code中有註解),所以第一個PCD是從1開始,只是後面會將Token Number減1,再代入Table取得其相關資料。
#define _PCD_TOKEN_PcdFlashNvStorageFtwWorkingBase  1U
#define _PCD_GET_MODE_32_...  LibPcdGet32(_PCD_TOKEN_PcdFlashNvStorageFtwWorkingBase)
#define _PCD_SET_MODE_32_...(Value)  LibPcdSet32(_PCD_TOKEN_PcdFlashNvStorageFtwWorkingBase, (Value))

#define _PCD_TOKEN_PcdFlashNvStorageFtwSpareBase  2U
#define _PCD_GET_MODE_32_...  LibPcdGet32(_PCD_TOKEN_PcdFlashNvStorageFtwSpareBase)
#define _PCD_SET_MODE_32_...(Value)  LibPcdSet32(_PCD_TOKEN_PcdFlashNvStorageFtwSpareBase, (Value))

#define _PCD_TOKEN_PcdFlashNvStorageVariableBase  3U
#define _PCD_GET_MODE_32_...  LibPcdGet32(_PCD_TOKEN_PcdFlashNvStorageVariableBase)
#define _PCD_SET_MODE_32_...(Value)  LibPcdSet32(_PCD_TOKEN_PcdFlashNvStorageVariableBase, (Value))

#define _PCD_TOKEN_PcdS3BootScriptTablePrivateDataPtr  4U
#define _PCD_GET_MODE_64_...  LibPcdGet64(_PCD_TOKEN_PcdS3BootScriptTablePrivateDataPtr)
#define _PCD_SET_MODE_64_...(Value)  LibPcdSet64(_PCD_TOKEN_PcdS3BootScriptTablePrivateDataPtr, (Value))

在PPI或Protocol的底層會透過呼叫GetWorkerSetWorker來存取PCD。

GetWorker:


VOID *
GetWorker (
  IN UINTN               TokenNumber,
  IN UINTN               GetSize
  )

透過Token Number取得該PCD的LocalTokenNumber (Offset、PCD Type、Data Type)。
LocalTokenNumber = PeiPcdDb->Init.LocalTokenNumberTable[TokenNumber];

透過Mask單獨取得Offset。
Offset = LocalTokenNumber & PCD_DATABASE_OFFSET_MASK;

接下來會依照各種不同PCD Type做不同的處理:
PCD_TYPE_VPD:
傳回VPD (Vital Product Data)中特定的Offset的資料。
VPD_HEAD *VpdHead;
VpdHead = (VPD_HEAD *) ((UINT8 *)PeiPcdDb + Offset);
return (VOID *) (UINTN) (PcdGet32 (PcdVpdBaseAddress) + VpdHead->Offset);


PCD_TYPE_HII|PCD_TYPE_STRING:
PCD_TYPE_HII:
透過Offset取得Variable Head,裡面有著GUID與Name的Index。
VariableHead = (VARIABLE_HEAD *) ((UINT8 *)PeiPcdDb + Offset);

Guid = &(PeiPcdDb->Init.GuidTable[VariableHead->GuidTableIndex]);
Name = (UINT16*)&StringTable[VariableHead->StringIndex];


透過GetVariable取得Variable Data。
Status = GetHiiVariable (Guid, Name, &Data, &DataSize);

如有錯誤則去取得PCD設定的預設值(如:NOT_FOUND)。
if (Status == EFI_SUCCESS) {
  return (VOID *) ((UINT8 *) Data + VariableHead->Offset);
} else {
  //
  // Return the default value specified by Platform Integrator
  //
  if ((LocalTokenNumber & PCD_TYPE_ALL_SET) == (PCD_TYPE_HII|PCD_TYPE_STRING)) {
    return (VOID*)&StringTable[*(STRING_HEAD*)((UINT8*)PeiPcdDb + VariableHead->DefaultValueOffset)];
  } else {
    return (VOID *) ((UINT8 *) PeiPcdDb + VariableHead->DefaultValueOffset);
  }
}


PCD_TYPE_DATA:
直接透過Offset取得Data。
return (VOID *) ((UINT8 *)PeiPcdDb + Offset);

PCD_TYPE_STRING:
先取得該PCD的STRING_HEAD (StringTableIdx),裡面存放著對應StringTable的Offset。
StringTableIdx = * (STRING_HEAD*) ((UINT8 *) PeiPcdDb + Offset);
return (VOID *) (&StringTable[StringTableIdx]);


SetWorker:


EFI_STATUS
SetWorker (
  IN          UINTN               TokenNumber,
  IN          VOID                *Data,
  IN OUT      UINTN               *Size,
  IN          BOOLEAN             PtrType
  )

透過Token Number取得該PCD的LocalTokenNumber (Offset、PCD Type、Data Type)。
LocalTokenNumber = PeiPcdDb->Init.LocalTokenNumberTable[TokenNumber];

會先判斷Data Type是不是Pointer,通常像String之類的,會被歸類為Pointer。
要注意到在Build Tool會在PCD Database中,先宣告好固定長度的陣列來存放String等其它Data。
如果PtrType為True,而傳入的Size又大於原先預留好的最大值的話,就會造成問題,這邊就先擋掉並回傳MaxSize。
if (PtrType) {
  //
  // Get MaxSize first, then check new size with max buffer size.
  //
  GetPtrTypeSize (TokenNumber, &MaxSize, PeiPcdDb);
  if (*Size > MaxSize) {
    *Size = MaxSize;
    return EFI_INVALID_PARAMETER;
  }
} else {
  if (*Size != PeiPcdGetSize (TokenNumber + 1)) {
    return EFI_INVALID_PARAMETER;
  }
}


當某個PCD被修改時會去呼叫,為該PCD註冊Notify的Function。
可以透過PCD Library的LibPcdCallbackOnSet來進行註冊會比較方便。
if (TokenNumber + 1 < PEI_NEX_TOKEN_NUMBER + 1) {
  InvokeCallbackOnSet (0, NULL, TokenNumber + 1, Data, *Size);
}


透過Mask單獨取得Offset,並取得Data。
Offset = LocalTokenNumber & PCD_DATABASE_OFFSET_MASK;
InternalData    = (VOID *) ((UINT8 *) PeiPcdDb + Offset);


接下來會依照各種不同PCD Type做不同的處理:
PCD_TYPE_VPD:
PCD_TYPE_HII:
PCD_TYPE_HII|PCD_TYPE_STRING:
可能是上述幾個Type都不支援在PEI修改NVS,所以這邊就直接return回EFI_INVALID_PARAMETER。
尤其PCD_TYPE_HII實際上就是個Variable。
return EFI_INVALID_PARAMETER;

PCD_TYPE_STRING:
傳入的資料為一串Array,資料長度可能會變動,所以先更改Current Size。
SizeTable中有兩個欄位,一個是Max Size,一個是Current Size。
之後再透過CopyMem,將傳入的Data Copy到StringTable中。
if (SetPtrTypeSize (TokenNumber, Size, PeiPcdDb)) {
  StringTableIdx = *((STRING_HEAD *)InternalData);
  CopyMem (&PeiPcdDb->Init.StringTable[StringTableIdx], Data, *Size);
  return EFI_SUCCESS;
} else {
  return EFI_INVALID_PARAMETER;
}


PCD_TYPE_DATA:
PtrType的處理方式跟PCD_TYPE_STRING很像。
if (PtrType) {
  if (SetPtrTypeSize (TokenNumber, Size, PeiPcdDb)) {
    CopyMem (InternalData, Data, *Size);
    return EFI_SUCCESS;
  } else {
    return EFI_INVALID_PARAMETER;
  }
}


其餘基本型態只是透過轉型來處理。
switch (*Size) {
  case sizeof(UINT8):
    *((UINT8 *) InternalData) = *((UINT8 *) Data);
    return EFI_SUCCESS;

  case sizeof(UINT16):
    *((UINT16 *) InternalData) = *((UINT16 *) Data);
    return EFI_SUCCESS;

  case sizeof(UINT32):
    *((UINT32 *) InternalData) = *((UINT32 *) Data);
    return EFI_SUCCESS;

  case sizeof(UINT64):
    *((UINT64 *) InternalData) = *((UINT64 *) Data);
    return EFI_SUCCESS;

  default:
    ASSERT (FALSE);
    return EFI_NOT_FOUND;
}