【UEFI基础】EDK网络框架(UNDI)

news2024/11/27 22:27:11

UNDI

UNDI代码综述

UNDI全称Universal Network Driver Interface,它虽然属于UEFI网络框架的一部分,但是并没有在EDK开源代码中实现。不过目前主流网卡厂商都会提供UEFI下的网络驱动,并且大部分都实现了UNDI,这样BIOS下就可以通过SNP来调用网卡的底层驱动。本节不会具体说明网卡驱动的实现,而将重点放在UNDI框架以及它与SNP的关系。

UNDI在UEFI网络协议栈中的关系图:

支持
提供
gEfiPciIoProtocolGuid
UNDI
gEfiNetworkInterfaceIdentifierProtocolGuid_31

SNP查询UNDI

UNDI说到底是UEFI规范中定义的一系列接口,然后SNP可以访问这些接口,达到网卡初始化和网络通信的目的。

SNP如何获取到这些接口呢?这需要实现了UNDI的网络设备驱动在初始化时安装一个Network Interface Identifier(NII)协议,目前它的版本是3_10:

///
/// An optional protocol that is used to describe details about the software
/// layer that is used to produce the Simple Network Protocol.
///
struct _EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL {
  UINT64     Revision;      ///< The revision of the EFI_NETWORK_INTERFACE_IDENTIFIER protocol.
  UINT64     Id;            ///< The address of the first byte of the identifying structure for this network
                            ///< interface. This is only valid when the network interface is started
                            ///< (see Start()). When the network interface is not started, this field is set to zero.
  UINT64     ImageAddr;     ///< The address of the first byte of the identifying structure for this
                            ///< network interface.  This is set to zero if there is no structure.
  UINT32     ImageSize;     ///< The size of unrelocated network interface image.
  CHAR8      StringId[4];   ///< A four-character ASCII string that is sent in the class identifier field of
                            ///< option 60 in DHCP. For a Type of EfiNetworkInterfaceUndi, this field is UNDI.
  UINT8      Type;          ///< Network interface type. This will be set to one of the values
                            ///< in EFI_NETWORK_INTERFACE_TYPE.
  UINT8      MajorVer;      ///< Major version number.
  UINT8      MinorVer;      ///< Minor version number.
  BOOLEAN    Ipv6Supported; ///< TRUE if the network interface supports IPv6; otherwise FALSE.
  UINT16     IfNum;         ///< The network interface number that is being identified by this Network
                            ///< Interface Identifier Protocol. This field must be less than or
                            ///< equal to the (IFcnt | IFcntExt <<8 ) fields in the !PXE structure.
};

SNP就可以通过对应的GUID来访问到它,代码如下(位于NetworkPkg\SnpDxe\Snp.c):

  //
  // Get the NII interface.
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                  (VOID **)&Nii,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );

  DEBUG ((DEBUG_INFO, "Start(): UNDI3.1 found\n"));

  Pxe = (PXE_UNDI *)(UINTN)(Nii->Id);

注意代码最后的强转,得到了NII中最重要的结构体PXE_UNDI

关于NII的安装,以Intel的网卡源码GigUndiDxe\Init.c为例,可以找到如下的代码:

EFI_STATUS
InitNiiProtocol (
  IN   UNDI_PRIVATE_DATA    *UndiPrivateData
  )
{
  NiiProtocol31                 = &UndiPrivateData->NiiProtocol31;
  NiiProtocol31->Id             = (UINT64) (UINTN) mE1000Pxe31;	// 这个就是PXE_UNDI
  NiiProtocol31->IfNum          = UndiPrivateData->IfId;

  NiiProtocol31->Revision       = EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL_REVISION_31;
  NiiProtocol31->Type           = EfiNetworkInterfaceUndi;
  NiiProtocol31->MajorVer       = PXE_ROMID_MAJORVER;
  NiiProtocol31->MinorVer       = PXE_ROMID_MINORVER_31;
  NiiProtocol31->ImageSize      = 0;
  NiiProtocol31->ImageAddr      = 0;
  NiiProtocol31->Ipv6Supported  = TRUE;

  NiiProtocol31->StringId[0]    = 'U';
  NiiProtocol31->StringId[1]    = 'N';
  NiiProtocol31->StringId[2]    = 'D';
  NiiProtocol31->StringId[3]    = 'I';

  Status = gBS->InstallMultipleProtocolInterfaces (
                  &UndiPrivateData->DeviceHandle,
                  &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                  NiiProtocol31,
                  NULL
                );
}

PXE_UNDI

PXE_UNDI结构体格式如下:

typedef union u_pxe_undi {
  PXE_HW_UNDI hw;
  PXE_SW_UNDI sw;
} PXE_UNDI;

可以看到它存在两种类型,从字面意思上看一种是硬件的格式,一种是软件的格式。对应的结构体描述如下(这种结构体有一个奇怪的名字叫!PXE,不知道这里的叹号表示什么意思。由于BIOS下PXE主要用来形容一种网络启动方式,所以这里的!PXE似乎是想说明这是一种“并不是用来启动的网络”?):

在这里插入图片描述

从图中可以看出来,硬件UNDI和软件UNDI有一个重要区别:即硬件UNDI通过往MMIO或者IO寄存器写命令(Command)来调用底层接口,而软件UNDI通过网络设备驱动提供出来的入口(Entry Point)来调用底层接口。UEFI下主要关注的是S/W的版本:

typedef struct s_pxe_sw_undi {
  PXE_UINT32    Signature;      ///< PXE_ROMID_SIGNATURE.
  PXE_UINT8     Len;            ///< sizeof(PXE_SW_UNDI).
  PXE_UINT8     Fudge;          ///< makes 8-bit cksum zero.
  PXE_UINT8     Rev;            ///< PXE_ROMID_REV.
  PXE_UINT8     IFcnt;          ///< physical connector count lower byte.
  PXE_UINT8     MajorVer;       ///< PXE_ROMID_MAJORVER.
  PXE_UINT8     MinorVer;       ///< PXE_ROMID_MINORVER.
  PXE_UINT8     IFcntExt;       ///< physical connector count upper byte.
  PXE_UINT8     reserved1;      ///< zero, not used.
  PXE_UINT32    Implementation; ///< Implementation flags.
  PXE_UINT64    EntryPoint;     ///< API entry point.
  PXE_UINT8     reserved2[3];   ///< zero, not used.
  PXE_UINT8     BusCnt;         ///< number of bustypes supported.
  PXE_UINT32    BusType[1];     ///< list of supported bustypes.
} PXE_SW_UNDI;

从目前SNP的实现来看,硬件UNDI并不支持:

  if ((Pxe->hw.Implementation & PXE_ROMID_IMP_HW_UNDI) != 0) {
    Snp->IsSwUndi             = FALSE;
    Snp->IssueUndi32Command   = &IssueHwUndiCommand;
  } else {
    Snp->IsSwUndi = TRUE;
 
    if ((Pxe->sw.Implementation & PXE_ROMID_IMP_SW_VIRT_ADDR) != 0) {
      Snp->IssueUndi32Command = (ISSUE_UNDI32_COMMAND) (UINTN) Pxe->sw.EntryPoint;
    } else {
      Snp->IssueUndi32Command = (ISSUE_UNDI32_COMMAND) (UINTN) ((UINT8) (UINTN) Pxe + Pxe->sw.EntryPoint);
    }
  }

因为这里的IssueHwUndiCommand()并没有实现:

EFI_STATUS
EFIAPI
IssueHwUndiCommand (
  UINT64  Cdb
  )
{
  DEBUG ((DEBUG_ERROR, "\nIssueHwUndiCommand() - This should not be called!"));

  if (Cdb == 0) {
    return EFI_INVALID_PARAMETER;
  }

  //
  //  %%TBD - For now, nothing is done.
  //
  return EFI_UNSUPPORTED;
}

所以目前BIOS下使用的都是软件UNDI,它的接口是从!PXE这个结构体中获取的,所以最终在SNP模块中使用IssueUndi32Command()函数相当于调用UNDI中的某个EntryPoint。

SNP调用UNDI

IssueUndi32Command()函数的声明如下:

typedef
EFI_STATUS
(EFIAPI *ISSUE_UNDI32_COMMAND)(
  UINT64         Cdb
  );

它接受一个参数cdb,虽然从这里看类型是UINT64,但是其实它是一个指针,指向结构体PXE_CDB,其描述如下:

在这里插入图片描述

对应到代码中的结构体是PXE_CDB

typedef struct s_pxe_cdb {
  PXE_OPCODE       OpCode;
  PXE_OPFLAGS      OpFlags;
  PXE_UINT16       CPBsize;
  PXE_UINT16       DBsize;
  PXE_UINT64       CPBaddr;
  PXE_UINT64       DBaddr;
  PXE_STATCODE     StatCode;
  PXE_STATFLAGS    StatFlags;
  PXE_UINT16       IFnum;
  PXE_CONTROL      Control;
} PXE_CDB;

下面简单说明:

  • OpCode是操作码,不同的操作码对OpFlags、CPB结构体、DB结构体(就是CPBxxx,DBxxx那几个成员,它们对应到结构体中)都会有影响。
  • StatCodeStatFlags是返回的参数,也受到OpCode的影响。
  • IFnum用来处理一个NII对应多个物理网络设备的情况,值从0开始,算是一个Index。
  • Control可以指示使用了一个CDB还是多个,还可以指示当操作忙时是等待命令执行还是直接返回失败。

OpCode的值可以在UefiPxe.h中找到:

///
/// Return UNDI operational state.
///
#define PXE_OPCODE_GET_STATE  0x0000

///
/// Change UNDI operational state from Stopped to Started.
///
#define PXE_OPCODE_START  0x0001

///
/// Change UNDI operational state from Started to Stopped.
///
#define PXE_OPCODE_STOP 0x0002

///
/// Get UNDI initialization information.
///
#define PXE_OPCODE_GET_INIT_INFO  0x0003

///
/// Get NIC configuration information.
///
#define PXE_OPCODE_GET_CONFIG_INFO  0x0004

///
/// Changed UNDI operational state from Started to Initialized.
///
#define PXE_OPCODE_INITIALIZE 0x0005

///
/// Re-initialize the NIC H/W.
///
#define PXE_OPCODE_RESET  0x0006

///
/// Change the UNDI operational state from Initialized to Started.
///
#define PXE_OPCODE_SHUTDOWN 0x0007

///
/// Read & change state of external interrupt enables.
///
#define PXE_OPCODE_INTERRUPT_ENABLES  0x0008

///
/// Read & change state of packet receive filters.
///
#define PXE_OPCODE_RECEIVE_FILTERS  0x0009

///
/// Read & change station MAC address.
///
#define PXE_OPCODE_STATION_ADDRESS  0x000A

///
/// Read traffic statistics.
///
#define PXE_OPCODE_STATISTICS 0x000B

///
/// Convert multicast IP address to multicast MAC address.
///
#define PXE_OPCODE_MCAST_IP_TO_MAC  0x000C

///
/// Read or change non-volatile storage on the NIC.
///
#define PXE_OPCODE_NVDATA 0x000D

///
/// Get & clear interrupt status.
///
#define PXE_OPCODE_GET_STATUS 0x000E

///
/// Fill media header in packet for transmit.
///
#define PXE_OPCODE_FILL_HEADER  0x000F

///
/// Transmit packet(s).
///
#define PXE_OPCODE_TRANSMIT 0x0010

///
/// Receive packet.
///
#define PXE_OPCODE_RECEIVE  0x0011

///
/// Last valid PXE UNDI OpCode number.
///
#define PXE_OPCODE_LAST_VALID 0x0011

这里包含了所有需要传递从SNP传递给UNDI的信息索引,不过并不是所有网卡都支持这些操作。

然后简单说明SNP调用UNDI的流程,如下图所示:

在这里插入图片描述

从软件来看,实际上就是下面的几个步骤:

  1. 填充CDB。
  2. 调用Snp->IssueUndi32Command(),参数就是CDB。
  3. 判断返回值。

以SNP中的PxeStart()函数为例:

/**
  Call UNDI to start the interface and changes the snp state.

  @param  Snp                    pointer to snp driver structure.

  @retval EFI_SUCCESS            UNDI is started successfully.
  @retval EFI_DEVICE_ERROR       UNDI could not be started.

**/
EFI_STATUS
PxeStart (
  IN SNP_DRIVER  *Snp
  )
{
  PXE_CPB_START_31  *Cpb31;

  Cpb31 = Snp->Cpb;
  //
  // Initialize UNDI Start CDB for H/W UNDI
  //
  Snp->Cdb.OpCode    = PXE_OPCODE_START;
  Snp->Cdb.OpFlags   = PXE_OPFLAGS_NOT_USED;
  Snp->Cdb.CPBsize   = PXE_CPBSIZE_NOT_USED;
  Snp->Cdb.DBsize    = PXE_DBSIZE_NOT_USED;
  Snp->Cdb.CPBaddr   = PXE_CPBADDR_NOT_USED;
  Snp->Cdb.DBaddr    = PXE_DBADDR_NOT_USED;
  Snp->Cdb.StatCode  = PXE_STATCODE_INITIALIZE;
  Snp->Cdb.StatFlags = PXE_STATFLAGS_INITIALIZE;
  Snp->Cdb.IFnum     = Snp->IfNum;
  Snp->Cdb.Control   = PXE_CONTROL_LAST_CDB_IN_LIST;

  //
  // Make changes to H/W UNDI Start CDB if this is
  // a S/W UNDI.
  //
  if (Snp->IsSwUndi) {
    Snp->Cdb.CPBsize = (UINT16)sizeof (PXE_CPB_START_31);
    Snp->Cdb.CPBaddr = (UINT64)(UINTN)Cpb31;

    Cpb31->Delay = (UINT64)(UINTN)&SnpUndi32CallbackDelay;
    Cpb31->Block = (UINT64)(UINTN)&SnpUndi32CallbackBlock;

    //
    // Virtual == Physical.  This can be set to zero.
    //
    Cpb31->Virt2Phys = (UINT64)(UINTN)0;
    Cpb31->Mem_IO    = (UINT64)(UINTN)&SnpUndi32CallbackMemio;

    Cpb31->Map_Mem   = (UINT64)(UINTN)&SnpUndi32CallbackMap;
    Cpb31->UnMap_Mem = (UINT64)(UINTN)&SnpUndi32CallbackUnmap;
    Cpb31->Sync_Mem  = (UINT64)(UINTN)&SnpUndi32CallbackSync;

    Cpb31->Unique_ID = (UINT64)(UINTN)Snp;
  }

  //
  // Issue UNDI command and check result.
  //
  DEBUG ((DEBUG_NET, "\nsnp->undi.start()  "));

  (*Snp->IssueUndi32Command)((UINT64)(UINTN)&Snp->Cdb);

  if (Snp->Cdb.StatCode != PXE_STATCODE_SUCCESS) {
    //
    // UNDI could not be started. Return UNDI error.
    //
    DEBUG (
      (DEBUG_ERROR,
       "\nsnp->undi.start()  %xh:%xh\n",
       Snp->Cdb.StatCode,
       Snp->Cdb.StatFlags)
      );

    return EFI_DEVICE_ERROR;
  }

  //
  // Set simple network state to Started and return success.
  //
  Snp->Mode.State = EfiSimpleNetworkStarted;

  return EFI_SUCCESS;
}

SNP中的所有操作,实际上到最后都是使用类似上述的方式来完成的。最后简单说明CPB和DB,CPB的结构如下:

typedef struct s_pxe_cpb_start_31 {
  ///
  /// PXE_VOID Delay(UINT64 UnqId, UINTN microseconds);
  ///
  /// UNDI will never request a delay smaller than 10 microseconds
  /// and will always request delays in increments of 10 microseconds.
  /// The Delay() CallBack routine must delay between n and n + 10
  /// microseconds before returning control to the UNDI.
  ///
  /// This field cannot be set to zero.
  ///
  UINT64    Delay;

  ///
  /// PXE_VOID Block(UINT64 unq_id, UINT32 enable);
  ///
  /// UNDI may need to block multi-threaded/multi-processor access to
  /// critical code sections when programming or accessing the network
  /// device.  To this end, a blocking service is needed by the UNDI.
  /// When UNDI needs a block, it will call Block() passing a non-zero
  /// value.  When UNDI no longer needs a block, it will call Block()
  /// with a zero value.  When called, if the Block() is already enabled,
  /// do not return control to the UNDI until the previous Block() is
  /// disabled.
  ///
  /// This field cannot be set to zero.
  ///
  UINT64    Block;

  ///
  /// PXE_VOID Virt2Phys(UINT64 UnqId, UINT64 virtual, UINT64 physical_ptr);
  ///
  /// UNDI will pass the virtual address of a buffer and the virtual
  /// address of a 64-bit physical buffer.  Convert the virtual address
  /// to a physical address and write the result to the physical address
  /// buffer.  If virtual and physical addresses are the same, just
  /// copy the virtual address to the physical address buffer.
  ///
  /// This field can be set to zero if virtual and physical addresses
  /// are equal.
  ///
  UINT64    Virt2Phys;
  ///
  /// PXE_VOID Mem_IO(UINT64 UnqId, UINT8 read_write, UINT8 len, UINT64 port,
  ///              UINT64 buf_addr);
  ///
  /// UNDI will read or write the device io space using this call back
  /// function. It passes the number of bytes as the len parameter and it
  /// will be either 1,2,4 or 8.
  ///
  /// This field can not be set to zero.
  ///
  UINT64    Mem_IO;
  ///
  /// PXE_VOID Map_Mem(UINT64 unq_id, UINT64 virtual_addr, UINT32 size,
  ///                 UINT32 Direction, UINT64 mapped_addr);
  ///
  /// UNDI will pass the virtual address of a buffer, direction of the data
  /// flow from/to the mapped buffer (the constants are defined below)
  /// and a place holder (pointer) for the mapped address.
  /// This call will Map the given address to a physical DMA address and write
  /// the result to the mapped_addr pointer.  If there is no need to
  /// map the given address to a lower address (i.e. the given address is
  /// associated with a physical address that is already compatible to be
  /// used with the DMA, it converts the given virtual address to it's
  /// physical address and write that in the mapped address pointer.
  ///
  /// This field can be set to zero if there is no mapping service available.
  ///
  UINT64    Map_Mem;

  ///
  /// PXE_VOID UnMap_Mem(UINT64 unq_id, UINT64 virtual_addr, UINT32 size,
  ///            UINT32 Direction, UINT64 mapped_addr);
  ///
  /// UNDI will pass the virtual and mapped addresses of a buffer.
  /// This call will un map the given address.
  ///
  /// This field can be set to zero if there is no unmapping service available.
  ///
  UINT64    UnMap_Mem;

  ///
  /// PXE_VOID Sync_Mem(UINT64 unq_id, UINT64 virtual,
  ///            UINT32 size, UINT32 Direction, UINT64 mapped_addr);
  ///
  /// UNDI will pass the virtual and mapped addresses of a buffer.
  /// This call will synchronize the contents of both the virtual and mapped.
  /// buffers for the given Direction.
  ///
  /// This field can be set to zero if there is no service available.
  ///
  UINT64    Sync_Mem;

  ///
  /// protocol driver can provide anything for this Unique_ID, UNDI remembers
  /// that as just a 64bit value associated to the interface specified by
  /// the ifnum and gives it back as a parameter to all the call-back routines
  /// when calling for that interface!
  ///
  UINT64    Unique_ID;
} PXE_CPB_START_31;

虽然看到的都是UINT64的成员,但是它们其实都是一个个的函数指针,通过它UEFI可以给网卡驱动提供一些最基础的操作函数,比如延时操作,内存读写操作,IO读写操作等,这样的目的是为了能够处理不同的平台,上述基本操作的底层实现在不同的平台可能不同。

DB对应的结构体有很多,它表示的是UNDI的返回值,由于返回值不同,所以对应的结构体也会跟着改变,比如获取状态的结构体是这样的:

typedef struct s_pxe_db_get_status {
  ///
  /// Length of next receive frame (header + data).  If this is zero,
  /// there is no next receive frame available.
  ///
  PXE_UINT32    RxFrameLen;

  ///
  /// Reserved, set to zero.
  ///
  PXE_UINT32    reserved;

  ///
  ///  Addresses of transmitted buffers that need to be recycled.
  ///
  PXE_UINT64    TxBuffer[MAX_XMIT_BUFFERS];
} PXE_DB_GET_STATUS;

获取MAC地址的结构体是这样的:

typedef struct s_pxe_db_mcast_ip_to_mac {
  ///
  /// Multicast MAC address.
  ///
  PXE_MAC_ADDR    MAC;
} PXE_DB_MCAST_IP_TO_MAC;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1357048.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鸿蒙APP上线注意事项

在将鸿蒙APP上线之前&#xff0c;开发者需要注意一些关键的事项&#xff0c;以确保应用的顺利发布和良好运营。以下是一些建议的注意事项&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.遵循应用市…

【常用排序算法】冒泡排序

冒泡排序 冒泡排序基本思想&#xff1a;N 个数的数组&#xff0c;经过N-1轮排序。 升序 大的值下沉&#xff0c;小的值上浮。降序 小的值下沉&#xff0c;小的字上浮 import java.util.Arrays; public class BubbleSort {public static void main(String[] args) {int[] values…

电脑视频需要分屏怎么做

在当今数字时代&#xff0c;人们对于视频的需求越来越高。有时候&#xff0c;我们可能想在同一屏幕上同时播放多个视频&#xff0c;进行对比、观看、剪辑或者其他目的。那么&#xff0c;视频分屏应该怎么做呢&#xff1f; 在本篇文章中&#xff0c;我们将会详细的为你介绍视频分…

Geotrust DV通配符证书保护域名数量

Geotrust是一家知名的SSL证书提供商&#xff0c;旗下有多种类型的SSL数字证书&#xff0c;保护网站数据在传输过程中的安全性和完整性&#xff0c;帮助用户确认其网站的安全。通配符SSL证书是Geotrust颁发的一种可以同时保护多个域名站点的SSL证书。今天就随SSL盾小编了解Geotr…

conda虚拟环境搭建和打包,删除,移动等全流程及相关问题汇总

私人笔记无偿分享&#xff0c;更多内容请访问&#xff1a;链接&#xff1a;https://pan.baidu.com/s/19mS5N9XJ_AotF20kUwSA3w?pwdp5kx 提取码&#xff1a;p5kx 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 4.4. anaconda虚拟环境搭建&#xff1a; 网址&am…

解决在test以外的目录下导入junit无效

以上引用来自src目录下的文件&#xff0c;可以看到&#xff0c;和junit有关的导入都飘红&#xff0c;但明明junit已经被正确导入进了项目中。 再看右侧的Maven的依赖下方&#xff0c;junit的右边有一个很不起眼的(test) 这是因为junit作为测试框架&#xff0c;可能包含仅适用于…

Halcon区域的面积和中心点area_center

Halcon区域的面积和中心点 提到区域的特征&#xff0c;最常用的莫过于区域的面积和中心点坐标信息。实际工作中&#xff0c;经常会使用面积或中心点进行特征的选择和定位。Halcon中的area_center算子就是用于实现这一功能的&#xff0c;该算子一次返回以下两个结果。 &#xf…

【KingbaseES】实现MySql函数WEEKS_BETWEEN

WEEKS_BETWEEN CREATE OR REPLACE FUNCTION weeks_between(start_date date, end_date date) RETURNS integer AS $$ BEGIN RETURN EXTRACT(WEEK FROM end_date) - EXTRACT(WEEK FROM start_date); END; $$ LANGUAGE plpgsql IMMUTABLE;结果展示

嵌入式Linux之MX6ULL裸机开发学习笔记(IMX启动方式-启动设备的选择)

一,硬件启动方式选择 1.启动方式的选择 6ull支持多种启动方式。 比如可以从 SD/EMMC、 NAND Flash、 QSPI Flash等启动。 6ull是怎么支持多种外置flash启动程序的。 1.启动方式选择&#xff1a; BOOT_MODE0 and BOOT_MODE1&#xff0c;这两个是两个IO来控制的&#xff0c;…

Unity 使用Sprite绘制一条自定义图片的线

Unity 使用Sprite绘制一条自定义图片的线 前言项目场景布置代码编写总结 运行效果感谢 前言 遇到一个需要绘制自定义形状的需求。那只能利用Sprite来绘制一条具有自定义图片的线&#xff0c;通过代码动态设置起点、终点以及线宽&#xff0c;实现灵活的线条效果。 项目 场景…

嵌入式Linux之MX6ULL裸机开发学习笔记(汇编LED灯点亮)

汇编LED驱动实验 1.驱动编写 首先创建在vscode上创建工作区&#xff0c;创建led.s汇编文件&#xff0c;然后编写以下程序 .global _start 全局标号 _start: /* 使能所有外设时钟 */ ldr r0,0x020c4068 CCGR0 ldr r1,0xffffffff 要向CCGR0写入的数据 str r1,[r0] 将0xff…

UE5 C++(十一)— 碰撞检测

文章目录 代理绑定BeginOverlap和EndOverlapHit事件的代理绑定碰撞设置 代理绑定BeginOverlap和EndOverlap 首先&#xff0c;创建自定义ActorC类 MyCustomActor 添加碰撞组件 #include "Components/BoxComponent.h"public:UPROPERTY(VisibleAnywhere, BlueprintRea…

IO进程线程 day4

进程状态间的转化 创建出三个进程完成两个文件之间拷贝工作&#xff0c;子进程1拷贝前一半内容&#xff0c;子进程2拷贝后一半内容&#xff0c;父进程回收子进程的资源 #include <head.h> int main(int argc, const char *argv[]) {FILE *fp1NULL,*fp2NULL;//定义两个文…

Java Arrays.copyOfRange的用法

Arrays.copyOfRange的使用方法&#xff1a; 将一个数组拷贝至另一个数组中 参数&#xff1a; original&#xff1a;第一个参数为要拷贝的数组对象 from&#xff1a;第二个参数为拷贝的开始位置&#xff08;包含&#xff09; to&#xff1a;第三个参数为拷贝的结束位置&#x…

小程序组件内的数据监听器

数据监听器可以用于监听和响应任何属性和数据字段的变化。从小程序基础库版本 2.6.1 开始支持。 有时&#xff0c;在一些数据字段被 setData 设置时&#xff0c;需要执行一些操作。例如&#xff0c; 一个值取决于另外两个值的变化&#xff0c;this.data.sum 永远是 this.data.…

大创项目推荐 深度学习卷积神经网络垃圾分类系统 - 深度学习 神经网络 图像识别 垃圾分类 算法 小程序

文章目录 0 简介1 背景意义2 数据集3 数据探索4 数据增广(数据集补充)5 垃圾图像分类5.1 迁移学习5.1.1 什么是迁移学习&#xff1f;5.1.2 为什么要迁移学习&#xff1f; 5.2 模型选择5.3 训练环境5.3.1 硬件配置5.3.2 软件配置 5.4 训练过程5.5 模型分类效果(PC端) 6 构建垃圾…

微软 Power Platform 使用Power Automate发送邮件以Dataverse作为数据源的附件File Column

微软Power Platform使用Power Automate发送邮件添加Power Apps以Dataverse作为数据源的附件File Column方式 目录 微软Power Platform使用Power Automate发送邮件添加Power Apps以Dataverse作为数据源的附件File Column方式1、需求背景介绍2、附件列File Column介绍3、如何在Po…

几种读nii图像方法的轴序比较

读 .nii / .nii.gz 图像并转成 numpy 可用 medpy.io、nibabel、itk、SimpleITK 几种方法&#xff0c;然而几种方法读出来的轴序有出入&#xff0c;本篇比较此几种方法。 Datum 所用数据来自 verse&#xff0c;经 iTomxy/data/verse/preprocess.py 预处理&#xff0c;朝向和轴…

Linux_apachectl 网页优化

1.1 网页压缩与缓存 在使用 Apache 作为 Web 服务器的过程中&#xff0c;只有对 Apache 服务器进行适当的优化配 置&#xff0c;才能让 Apache 发挥出更好的性能。反过来说&#xff0c;如果 Apache 的配置非常糟糕&#xff0c; Apache 可能无法正常为我们服务。因此&#xff0c…

【技能---500G硬盘-Ubuntu 20.04安装分区参考】

文章目录 Ubuntu 20.04安装分区指导安装分区流程Ubuntu 系统分区关键一步----- 选择安装启动引导器的设备 Ubuntu 20.04安装分区指导 安装Ubuntu 20.04的时候可以自己指定各个内存空间的占用&#xff0c;值得注意的是&#xff0c;这里的分区有一定的技巧&#xff01;&#xff0…