【UEFI】HiiDataBase 学习

2 篇文章 0 订阅
1 篇文章 0 订阅

概要

参考资料:efi-human-interface-infrastructure-specification-v092.pdf、SetupDesignGuide.pdf
Hii:Human Interface Infrastructure 的缩写。
其在UEFI中的体现是 BIOS Setup的内容显示。

gEfiHiiStringProtocolGuid、
gEfiHiiDatabaseProtocolGuid、
gEfiHiiConfigRoutingProtocolGuid,
//常用的几个Protocol

整体架构流程

HiiDataBase由多个PackageList组成,每个PackageList对应一个HiiHandle数据存放在一块连续内存,通过HiiHandle可以找到对应的PackageList;每个PackageList由多个Package组成。
大概架构

EFI_GUID gEfiHiiDatabaseProtocolGuid = {0xef9fc172, 0xa1b2, 0x4693, {0xb3, 0x27, 0x6d, 0x32, 0xfc, 0x41, 0x60, 0x42}};
EFI_HII_DATABASE_PROTOCOL
///
/// Database manager for HII-related data structures.
///
struct _EFI_HII_DATABASE_PROTOCOL {
  EFI_HII_DATABASE_NEW_PACK             NewPackageList;
  EFI_HII_DATABASE_REMOVE_PACK          RemovePackageList;
  EFI_HII_DATABASE_UPDATE_PACK          UpdatePackageList;
  EFI_HII_DATABASE_LIST_PACKS           ListPackageLists;
  EFI_HII_DATABASE_EXPORT_PACKS         ExportPackageLists;
  EFI_HII_DATABASE_REGISTER_NOTIFY      RegisterPackageNotify;
  EFI_HII_DATABASE_UNREGISTER_NOTIFY    UnregisterPackageNotify;
  EFI_HII_FIND_KEYBOARD_LAYOUTS         FindKeyboardLayouts;
  EFI_HII_GET_KEYBOARD_LAYOUT           GetKeyboardLayout;
  EFI_HII_SET_KEYBOARD_LAYOUT           SetKeyboardLayout;
  EFI_HII_DATABASE_GET_PACK_HANDLE      GetPackageListHandle;
};

常用的几个接口函数:ListPackageLists(导出HiiHandles)、ExportPackageLists(根据HiiHandle导出对应的PackageList)、GetPackageListHandle(获取HiiHandle对应的DriverHandle,用于获取DevicePath等用途)

/**

  This function returns a list of the package handles of the
  specified type that are currently active in the database. The
  pseudo-type EFI_HII_PACKAGE_TYPE_ALL will cause all package
  handles to be listed.

  @param This                 A pointer to the EFI_HII_DATABASE_PROTOCOL instance.

  @param PackageType          Specifies the package type of the packages
                              to list or EFI_HII_PACKAGE_TYPE_ALL for
                              all packages to be listed.

  @param PackageGuid          If PackageType is
                              EFI_HII_PACKAGE_TYPE_GUID, then this is
                              the pointer to the GUID which must match
                              the Guid field of
                              EFI_HII_PACKAGE_GUID_HEADER. Otherwise, it
                              must be NULL.

  @param HandleBufferLength   On input, a pointer to the length
                              of the handle buffer. On output,
                              the length of the handle buffer
                              that is required for the handles found.

  @param Handle               An array of EFI_HII_HANDLE instances returned.

  @retval EFI_SUCCESS           The matching handles are outputted successfully.
                                HandleBufferLength is updated with the actual length.
  @retval EFI_BUFFER_TOO_SMALL  The HandleBufferLength parameter
                                indicates that Handle is too
                                small to support the number of
                                handles. HandleBufferLength is
                                updated with a value that will
                                enable the data to fit.
  @retval EFI_NOT_FOUND         No matching handle could be found in database.
  @retval EFI_INVALID_PARAMETER HandleBufferLength was NULL.
  @retval EFI_INVALID_PARAMETER The value referenced by HandleBufferLength was not
                                zero and Handle was NULL.
  @retval EFI_INVALID_PARAMETER PackageType is not a EFI_HII_PACKAGE_TYPE_GUID but
                                PackageGuid is not NULL, PackageType is a EFI_HII_
                                PACKAGE_TYPE_GUID but PackageGuid is NULL.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_HII_DATABASE_LIST_PACKS)(
  IN CONST  EFI_HII_DATABASE_PROTOCOL *This,
  IN        UINT8                     PackageType,
  IN CONST  EFI_GUID                  *PackageGuid,
  IN OUT    UINTN                     *HandleBufferLength,
  OUT       EFI_HII_HANDLE            *Handle
  );

/**

  This function will export one or all package lists in the
  database to a buffer. For each package list exported, this
  function will call functions registered with EXPORT_PACK and
  then copy the package list to the buffer. The registered
  functions may call EFI_HII_DATABASE_PROTOCOL.UpdatePackageList()
  to modify the package list before it is copied to the buffer. If
  the specified BufferSize is too small, then the status
  EFI_OUT_OF_RESOURCES will be returned and the actual package
  size will be returned in BufferSize.

  @param This         A pointer to the EFI_HII_DATABASE_PROTOCOL instance.


  @param Handle       An EFI_HII_HANDLE  that corresponds to the
                      desired package list in the HII database to
                      export or NULL to indicate all package lists
                      should be exported.

  @param BufferSize   On input, a pointer to the length of the
                      buffer. On output, the length of the
                      buffer that is required for the exported
                      data.

  @param Buffer       A pointer to a buffer that will contain the
                      results of the export function.


  @retval EFI_SUCCESS           Package exported.

  @retval EFI_OUT_OF_RESOURCES  BufferSize is too small to hold the package.

  @retval EFI_NOT_FOUND         The specified Handle could not be found in the
                                current database.

  @retval EFI_INVALID_PARAMETER BufferSize was NULL.

  @retval EFI_INVALID_PARAMETER The value referenced by BufferSize was not zero
                                and Buffer was NULL.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_HII_DATABASE_EXPORT_PACKS)(
  IN CONST  EFI_HII_DATABASE_PROTOCOL      *This,
  IN        EFI_HII_HANDLE                 Handle,
  IN OUT    UINTN                          *BufferSize,
  OUT       EFI_HII_PACKAGE_LIST_HEADER    *Buffer
  );
  
/**

  Return the EFI handle associated with a package list.

  @param This               A pointer to the EFI_HII_PROTOCOL instance.

  @param PackageListHandle  An EFI_HII_HANDLE  that corresponds
                            to the desired package list in the
                            HIIdatabase.

  @param DriverHandle       On return, contains the EFI_HANDLE which
                            was registered with the package list in
                            NewPackageList().

  @retval EFI_SUCCESS            The DriverHandle was returned successfully.

  @retval EFI_INVALID_PARAMETER  The PackageListHandle was not valid.

**/
typedef
EFI_STATUS
(EFIAPI *EFI_HII_DATABASE_GET_PACK_HANDLE)(
  IN CONST  EFI_HII_DATABASE_PROTOCOL *This,
  IN        EFI_HII_HANDLE             PackageListHandle,
  OUT       EFI_HANDLE                *DriverHandle
  );

如何遍历?

遍历整个HiiDataBase首先从HiiHandles入手,因为每个HiiHandle就是一个PackageList,所以便利每个HiiHandle时导出一个PackageList。
EDK2中的例子有:

  //
  // Get all the Hii handles
  //
  HiiHandles = HiiGetHiiHandles (NULL);
  ASSERT (HiiHandles != NULL);
  //
  // Search for formset of each class type
  //
  for (Index = 0; HiiHandles[Index] != NULL; Index++) {
    Status = HiiGetFormSetFromHiiHandle (HiiHandles[Index], &Buffer, &BufferSize);
    if (EFI_ERROR (Status)) {
      continue;
    }



/**
  This function allows a caller to extract the form set opcode form the Hii Handle.
  The returned buffer is allocated using AllocatePool().The caller is responsible
  for freeing the allocated buffer using FreePool().

  @param Handle            The HII handle.
  @param Buffer            On return, points to a pointer which point to the buffer that contain the formset opcode.
  @param BufferSize        On return, points to the length of the buffer.

  @retval EFI_OUT_OF_RESOURCES   No enough memory resource is allocated.
  @retval EFI_NOT_FOUND          Can't find the package data for the input Handle.
  @retval EFI_INVALID_PARAMETER  The input parameters are not correct.
  @retval EFI_SUCCESS            Get the formset opcode from the hii handle successfully.

**/
EFI_STATUS
EFIAPI
HiiGetFormSetFromHiiHandle (
  IN  EFI_HII_HANDLE    Handle,
  OUT EFI_IFR_FORM_SET  **Buffer,
  OUT UINTN             *BufferSize
  )
{
  EFI_STATUS                   Status;
  UINTN                        PackageListSize;
  UINTN                        TempSize;
  EFI_HII_PACKAGE_LIST_HEADER  *HiiPackageList;
  UINT8                        *Package;
  UINT8                        *OpCodeData;
  UINT8                        *FormSetBuffer;
  UINT8                        *TempBuffer;
  UINT32                       Offset;
  UINT32                       Offset2;
  UINT32                       PackageListLength;
  EFI_HII_PACKAGE_HEADER       PackageHeader;

  TempSize      = 0;
  FormSetBuffer = NULL;
  TempBuffer    = NULL;

  //
  // Get HII PackageList
  //
  PackageListSize = 0;
  HiiPackageList  = NULL;
  Status          = gHiiDatabase->ExportPackageLists (gHiiDatabase, Handle, &PackageListSize, HiiPackageList);
  if (EFI_ERROR (Status) && (Status != EFI_BUFFER_TOO_SMALL)) {
    return Status;
  }

  HiiPackageList = AllocatePool (PackageListSize);
  if (HiiPackageList == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Status = gHiiDatabase->ExportPackageLists (gHiiDatabase, Handle, &PackageListSize, HiiPackageList);
  ASSERT_EFI_ERROR (Status);

  //
  // Get Form package from this HII package List
  //
  Status            = EFI_NOT_FOUND;
  Offset            = sizeof (EFI_HII_PACKAGE_LIST_HEADER);
  PackageListLength = ReadUnaligned32 (&HiiPackageList->PackageLength);

  while (Offset < PackageListLength) {
    Package = ((UINT8 *)HiiPackageList) + Offset;
    CopyMem (&PackageHeader, Package, sizeof (EFI_HII_PACKAGE_HEADER));
    Offset += PackageHeader.Length;

    if (PackageHeader.Type != EFI_HII_PACKAGE_FORMS) {
      continue;
    }

    //
    // Search FormSet Opcode in this Form Package
    //
    Offset2 = sizeof (EFI_HII_PACKAGE_HEADER);
    while (Offset2 < PackageHeader.Length) {
      OpCodeData = Package + Offset2;
      Offset2   += ((EFI_IFR_OP_HEADER *)OpCodeData)->Length;

      if (((EFI_IFR_OP_HEADER *)OpCodeData)->OpCode != EFI_IFR_FORM_SET_OP) {
        continue;
      }

      if (FormSetBuffer != NULL) {
        TempBuffer = ReallocatePool (
                       TempSize,
                       TempSize + ((EFI_IFR_OP_HEADER *)OpCodeData)->Length,
                       FormSetBuffer
                       );
        if (TempBuffer == NULL) {
          Status = EFI_OUT_OF_RESOURCES;
          goto Done;
        }

        CopyMem (TempBuffer + TempSize, OpCodeData, ((EFI_IFR_OP_HEADER *)OpCodeData)->Length);
        FormSetBuffer = NULL;
      } else {
        TempBuffer = AllocatePool (TempSize + ((EFI_IFR_OP_HEADER *)OpCodeData)->Length);
        if (TempBuffer == NULL) {
          Status = EFI_OUT_OF_RESOURCES;
          goto Done;
        }

        CopyMem (TempBuffer, OpCodeData, ((EFI_IFR_OP_HEADER *)OpCodeData)->Length);
      }

      TempSize     += ((EFI_IFR_OP_HEADER *)OpCodeData)->Length;
      FormSetBuffer = TempBuffer;

      Status = EFI_SUCCESS;
      //
      // One form package has one formset, exit current form package to search other form package in the packagelist.
      //
      break;
    }
  }

Done:
  FreePool (HiiPackageList);

  *BufferSize = TempSize;
  *Buffer     = (EFI_IFR_FORM_SET *)FormSetBuffer;

  return Status;
}
    if (PackageHeader.Type != EFI_HII_PACKAGE_FORMS) {
      continue;
    }
//**这里表示只要Type为 EFI_HII_PACKAGE_FORMS 的 PackageList 其余定义如下**
//
// Value of HII package type
//
#define EFI_HII_PACKAGE_TYPE_ALL           0x00
#define EFI_HII_PACKAGE_TYPE_GUID          0x01
#define EFI_HII_PACKAGE_FORMS              0x02
#define EFI_HII_PACKAGE_STRINGS            0x04
#define EFI_HII_PACKAGE_FONTS              0x05
#define EFI_HII_PACKAGE_IMAGES             0x06
#define EFI_HII_PACKAGE_SIMPLE_FONTS       0x07
#define EFI_HII_PACKAGE_DEVICE_PATH        0x08
#define EFI_HII_PACKAGE_KEYBOARD_LAYOUT    0x09
#define EFI_HII_PACKAGE_ANIMATIONS         0x0A
#define EFI_HII_PACKAGE_END                0xDF
#define EFI_HII_PACKAGE_TYPE_SYSTEM_BEGIN  0xE0
#define EFI_HII_PACKAGE_TYPE_SYSTEM_END    0xFF

进入PackageList后就是遍历Form Package,根据不同的OpCode做对应操作:

**((EFI_IFR_OP_HEADER *)OpCodeData)->OpCode != EFI_IFR_FORM_SET_OP**
//
// IFR Opcodes
//
#define EFI_IFR_FORM_OP                 0x01
#define EFI_IFR_SUBTITLE_OP             0x02
#define EFI_IFR_TEXT_OP                 0x03
#define EFI_IFR_IMAGE_OP                0x04
#define EFI_IFR_ONE_OF_OP               0x05
#define EFI_IFR_CHECKBOX_OP             0x06
#define EFI_IFR_NUMERIC_OP              0x07
#define EFI_IFR_PASSWORD_OP             0x08
#define EFI_IFR_ONE_OF_OPTION_OP        0x09
#define EFI_IFR_SUPPRESS_IF_OP          0x0A
#define EFI_IFR_LOCKED_OP               0x0B
#define EFI_IFR_ACTION_OP               0x0C
#define EFI_IFR_RESET_BUTTON_OP         0x0D
#define EFI_IFR_FORM_SET_OP             0x0E
#define EFI_IFR_REF_OP                  0x0F
#define EFI_IFR_NO_SUBMIT_IF_OP         0x10
#define EFI_IFR_INCONSISTENT_IF_OP      0x11
#define EFI_IFR_EQ_ID_VAL_OP            0x12
#define EFI_IFR_EQ_ID_ID_OP             0x13
#define EFI_IFR_EQ_ID_VAL_LIST_OP       0x14
#define EFI_IFR_AND_OP                  0x15
#define EFI_IFR_OR_OP                   0x16
#define EFI_IFR_NOT_OP                  0x17
#define EFI_IFR_RULE_OP                 0x18
#define EFI_IFR_GRAY_OUT_IF_OP          0x19
#define EFI_IFR_DATE_OP                 0x1A
#define EFI_IFR_TIME_OP                 0x1B
#define EFI_IFR_STRING_OP               0x1C
#define EFI_IFR_REFRESH_OP              0x1D
#define EFI_IFR_DISABLE_IF_OP           0x1E
#define EFI_IFR_ANIMATION_OP            0x1F
#define EFI_IFR_TO_LOWER_OP             0x20
#define EFI_IFR_TO_UPPER_OP             0x21
#define EFI_IFR_MAP_OP                  0x22
#define EFI_IFR_ORDERED_LIST_OP         0x23
#define EFI_IFR_VARSTORE_OP             0x24
#define EFI_IFR_VARSTORE_NAME_VALUE_OP  0x25
#define EFI_IFR_VARSTORE_EFI_OP         0x26
#define EFI_IFR_VARSTORE_DEVICE_OP      0x27
#define EFI_IFR_VERSION_OP              0x28
#define EFI_IFR_END_OP                  0x29
#define EFI_IFR_MATCH_OP                0x2A
#define EFI_IFR_GET_OP                  0x2B
#define EFI_IFR_SET_OP                  0x2C
#define EFI_IFR_READ_OP                 0x2D
#define EFI_IFR_WRITE_OP                0x2E
#define EFI_IFR_EQUAL_OP                0x2F
#define EFI_IFR_NOT_EQUAL_OP            0x30
#define EFI_IFR_GREATER_THAN_OP         0x31
#define EFI_IFR_GREATER_EQUAL_OP        0x32
#define EFI_IFR_LESS_THAN_OP            0x33
#define EFI_IFR_LESS_EQUAL_OP           0x34
#define EFI_IFR_BITWISE_AND_OP          0x35
#define EFI_IFR_BITWISE_OR_OP           0x36
#define EFI_IFR_BITWISE_NOT_OP          0x37
#define EFI_IFR_SHIFT_LEFT_OP           0x38
#define EFI_IFR_SHIFT_RIGHT_OP          0x39
#define EFI_IFR_ADD_OP                  0x3A
#define EFI_IFR_SUBTRACT_OP             0x3B
#define EFI_IFR_MULTIPLY_OP             0x3C
#define EFI_IFR_DIVIDE_OP               0x3D
#define EFI_IFR_MODULO_OP               0x3E
#define EFI_IFR_RULE_REF_OP             0x3F
#define EFI_IFR_QUESTION_REF1_OP        0x40
#define EFI_IFR_QUESTION_REF2_OP        0x41
#define EFI_IFR_UINT8_OP                0x42
#define EFI_IFR_UINT16_OP               0x43
#define EFI_IFR_UINT32_OP               0x44
#define EFI_IFR_UINT64_OP               0x45
#define EFI_IFR_TRUE_OP                 0x46
#define EFI_IFR_FALSE_OP                0x47
#define EFI_IFR_TO_UINT_OP              0x48
#define EFI_IFR_TO_STRING_OP            0x49
#define EFI_IFR_TO_BOOLEAN_OP           0x4A
#define EFI_IFR_MID_OP                  0x4B
#define EFI_IFR_FIND_OP                 0x4C
#define EFI_IFR_TOKEN_OP                0x4D
#define EFI_IFR_STRING_REF1_OP          0x4E
#define EFI_IFR_STRING_REF2_OP          0x4F
#define EFI_IFR_CONDITIONAL_OP          0x50
#define EFI_IFR_QUESTION_REF3_OP        0x51
#define EFI_IFR_ZERO_OP                 0x52
#define EFI_IFR_ONE_OP                  0x53
#define EFI_IFR_ONES_OP                 0x54
#define EFI_IFR_UNDEFINED_OP            0x55
#define EFI_IFR_LENGTH_OP               0x56
#define EFI_IFR_DUP_OP                  0x57
#define EFI_IFR_THIS_OP                 0x58
#define EFI_IFR_SPAN_OP                 0x59
#define EFI_IFR_VALUE_OP                0x5A
#define EFI_IFR_DEFAULT_OP              0x5B
#define EFI_IFR_DEFAULTSTORE_OP         0x5C
#define EFI_IFR_FORM_MAP_OP             0x5D
#define EFI_IFR_CATENATE_OP             0x5E
#define EFI_IFR_GUID_OP                 0x5F
#define EFI_IFR_SECURITY_OP             0x60
#define EFI_IFR_MODAL_TAG_OP            0x61
#define EFI_IFR_REFRESH_ID_OP           0x62
#define EFI_IFR_WARNING_IF_OP           0x63
#define EFI_IFR_MATCH2_OP               0x64

这些OpCode分别对应vfr文件的定义:


formset
  guid     = PLAT_OVER_MNGR_GUID,
  title    = STRING_TOKEN(STR_ENTRY_TITLE),
  help     = STRING_TOKEN(STR_TITLE_HELP),

  varstore PLAT_OVER_MNGR_DATA,
    varid = VARSTORE_ID_PLAT_OVER_MNGR,
    name  = Data,
    guid  = PLAT_OVER_MNGR_GUID;

  form formid = FORM_ID_DEVICE,
       title = STRING_TOKEN(STR_TITLE);

    text
      help   = STRING_TOKEN(STR_FIRST_REFRESH_HELP),
      text   = STRING_TOKEN(STR_FIRST_REFRESH),
      flags  = INTERACTIVE,
      key    = KEY_VALUE_DEVICE_REFRESH;

    checkbox varid = Data.PciDeviceFilter,
      prompt   = STRING_TOKEN(STR_PCI_DEVICE_FILTER_PROMPT),
      help     = STRING_TOKEN(STR_PCI_DEVICE_FILTER_HELP),
      flags    = INTERACTIVE,
      key      = KEY_VALUE_DEVICE_FILTER,
    endcheckbox;

    label FORM_ID_DEVICE;
    label LABEL_END;

    subtitle text = STRING_TOKEN(STR_NULL_STRING);

    goto FORM_ID_DEVICE,
      prompt  = STRING_TOKEN(STR_CLEAR_ALL),
      help    = STRING_TOKEN(STR_CLEAR_ALL_HELP),
      flags   = INTERACTIVE | RESET_REQUIRED,
      key     = KEY_VALUE_DEVICE_CLEAR;
  endform;

  form formid = FORM_ID_DRIVER,
       title = STRING_TOKEN(STR_TITLE);

    goto FORM_ID_DEVICE,
      prompt  = STRING_TOKEN(STR_GOTO_PREVIOUS),
      help    = STRING_TOKEN(STR_NULL_STRING),
      flags   = INTERACTIVE,
      key     = KEY_VALUE_DRIVER_GOTO_PREVIOUS;

    goto FORM_ID_ORDER,
      prompt  = STRING_TOKEN(STR_TITLE_ORDER),
      help    = STRING_TOKEN(STR_TITLE_ORDER_HELP),
      flags   = INTERACTIVE,
      key     = KEY_VALUE_DRIVER_GOTO_ORDER;

    label FORM_ID_DRIVER;
    label LABEL_END;

  endform;

  form formid = FORM_ID_ORDER,
       title = STRING_TOKEN(STR_TITLE);

    goto FORM_ID_DRIVER,
      prompt  = STRING_TOKEN(STR_GOTO_PREVIOUS),
      help    = STRING_TOKEN(STR_NULL_STRING),
      flags   = INTERACTIVE,
      key     = KEY_VALUE_ORDER_GOTO_PREVIOUS;

    label FORM_ID_ORDER;
    label LABEL_END;

    subtitle text = STRING_TOKEN(STR_NULL_STRING);

    text
      help   = STRING_TOKEN (STR_NULL_STRING),
      text   = STRING_TOKEN (STR_SAVE_AND_EXIT),
      flags  = INTERACTIVE | RESET_REQUIRED,
      key    = KEY_VALUE_ORDER_SAVE_AND_EXIT;
  endform;

endformset;
**//以及**
  efivarstore CONSOLE_PREF_VARSTORE_DATA,
    attribute = EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,  // EFI variable attributes
    name  = ConsolePref,
    guid  = CONSOLE_PREF_FORMSET_GUID;
  
  oneof varid = ConsolePref.Console,
        prompt      = STRING_TOKEN(STR_CONSOLE_PREF_SELECT_PROMPT),
        help        = STRING_TOKEN(STR_CONSOLE_PREF_SELECT_HELP),
        flags       = NUMERIC_SIZE_1 | INTERACTIVE,
        option text = STRING_TOKEN(STR_CONSOLE_PREF_GRAPHICAL), value = CONSOLE_PREF_GRAPHICAL, flags = DEFAULT;
        option text = STRING_TOKEN(STR_CONSOLE_PREF_SERIAL), value = CONSOLE_PREF_SERIAL, flags = 0;
  endoneof;
//通过HiiGetString 函数来提取对应StringId 所对应的内容。StringId == STRING_TOKEN(STR_CONSOLE_PREF_SERIAL)
String    = HiiGetString (gStringPackHandle, StringId, NULL);

小结

HiiDatabase保存了所有在Setup中显示的静态内容,动态内容有些设备无法获取到。注意:有些设备的PackageListGuid是固定的有些则是运行时生成的。

  • 13
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值