UEFI学习(四)-SuperIo的访问
一、什么是Super I/O?
Super I/O 芯片也叫 I/O 芯片。在 486 以上档次的主板上都有 I/O控制电路。因为在南桥这样的高速设备和串行、并行接口、软盘驱动器及键盘鼠标等大量低速设备之间必定存在资源的不匹配,而需要经过转换和管理。而 Super I/O 芯片则完成了该功能。
通常在硬件监控芯片硬件监控芯片中会整合超级 I/O 功能,可用于监控受监控对象的电压、温度、转速等。主板在附件中会提供某种软件,它和主板上的硬件配合使用就能实现对电压、温度、风扇转速等的监控,一旦检测到这些参数超出设定的指标时,它会自动作出调整,以保护元件的安全。常见的温度控制芯片有 ADT7463 等等;通用的通用硬件监控芯片有 Winbond W83697HF 和 W83627HF , SMSC 的 LPC47M172 , ITE 的IT8705F、IT8703F,ASUS 的 AS99172F 等等,这些芯片通常还整合了对于温度的监控需与温度传感元件配合;对风扇电机转速的监控,则需与 CPU 的散热风扇配合使用。
SIO是一个半可定制化的芯片,怎么说是半可定制化呢?比如上电时序,这一部分就是固化好的,而可定制化部分则是逻辑设备(Logic Device)部分。接入电源后SIO便根据固化的程序开始运作,等待power button触发。按下power button后SIO开始跑上电时序,CPU Reset后BIOS才开始跑,此时BIOS给SIO配置的Logic Device也才生效。
原链接:https://ay123.net/mystudy/673/
二、我们要用SuperIo实现什么
本文使用的SuperIo为NCT5581D,需要注意的是不同的厂家SuperIo的访问方式与配置都会有差异。学习的资料为对应的datasheet和program guide。
对于NCT5581D我们的学习目标为:
- 了解NCT5581D的访问机制,学习 SIO Logic Device 相关内容。编写 一个 UEFI App(SIO.EFI),用于读写 SIO Logical Device 寄存器。
命令格式
读: SIO.EFI ldn //显示 ldn 这个 Device 下面的 256 个寄存器值
写: SIO.EFI ldn,offset,value //把 value 值写到 ldn 这个 Device,地址为 offset - 了解NCT5581D的GPIO功能,选择一个 GPIO 引脚,用 SIO.EFI 工具控制输出电平。万用表测量,验证效果
- 了解NCT5581D的Hardware Monitor的相关内容与其功能,编写 HardWareMonit 工具(HWM.efi)。实现一些功能:
1、读取 CPU 温度、风扇转速、CPU 电压。循环显示出来
2、控制 CPU 风扇转。 - 了解NCT5581D的WDT功能,编写看门狗测试工具(WDT.efi)。
运行命令:
SIO.EFI nSec //设置 WDT Timer 为 nSec 秒。 nSec 秒后系统自动重启;实时显示 Timer 剩余时间
三、NCT5581D的访问机制
若要了解NCT5581D的访问机制需要对datasheet的第7章内容进行学习,从datasheet中可以了解到NCT5581D使用一个特殊的协议来访问配置寄存器来设置不同类型的配置。NCT5581D有十多个逻辑设备(from Logical Device 1 to Logical Device 16)如果将所有逻辑设备配置寄存器映射到正常的PC地址空间,则需要很大的地址空间。所以,NCT5581D使用两个I/O地址(2Eh/2Fh或4Eh/4Fh)映射所有配置寄存器,2Eh/2Fh或4Eh/4Fh作为index(索引)/data(数据)对进行对superio内部寄存器的访问。我们可以通过,对2Eh写入地址,去选中相应寄存器;而对2Fh进行读写数据,来达到我们对superio内部寄存器的目的。需要注意的是在NCT5581D中有一组全局寄存器位于0h-2fh的索引处,其中包含了整个芯片的信息和配置。而访问单个逻辑设备的控制寄存器的方法也很简单。只需将所需的逻辑设备号写入全局寄存器07h。索引为30h或更高的后续访问将直接访问到逻辑设备寄存器。
在我们开始着手去编写应用去验证功能之前,我们还需要了解一下,NCT5581D内部寄存器的结构,以便对上述提到的内容有更好的理解。

(其中A25/A26是访问HardwareMonitor的IOspace的index/data对,后面会解释。)
下面列出的是NCT5581D的全局寄存器,也就是对应于上图中00h-ffh的寄存器

从上图中的备注中可以看到全局变量寄存器CR20h和CR21h(注:CR的意思是control register就是控制寄存器的意思)中保存的是Chip-ID的高八位和低八位。其中低八位可能会因为版本变更而有变化,但高八位总是不变的。
看到这里我们可以对NCT5581D有一个验证的设想,我们可以通过上面描述的方式对全局变量寄存器CR20h和CR21h进行读取然后对照上表进行验证是否正确获得相应寄存器的值,从而证明上述的superio访问方式是正确的可行的。
配置顺序
根据第7章的内容想要读取寄存器的值需要按照一定的配置顺序:
- Enter the Extended Function Mode.(进入扩展功能模式)
- Configure the configuration registers(配置寄存器)
- Exit the Extended Function Mode.(退出扩展功能模式)
①进入扩展功能模式
要将芯片置入扩展功能模式,必须连续两次0x87写入应用于扩展功能启用注册器(EFERs,即2Eh或4Eh)。
②配置寄存器
芯片选择逻辑设备,并通过功能索引扩展功能数据寄存器(EFIR)和扩展功能数据寄存器(EFDR)激活所需的逻辑设备。EFIR与EFER位于相同的地址,而EFDR位于地址(EFIR+1)。首先,将逻辑设备编号(即0x07)写入EFIR,然后将所需的逻辑设备的编号写入EFDR。如果要访问芯片(全局)控制注册器,则不需要执行此步骤。其次,将逻辑设备中所需的配置寄存器的地址写入EFIR,然后通过EFDR写入(或读取)所需的配置寄存器。
③退出扩展功能模式
要退出扩展功能模式,需要向EFER写入0xAA。一旦芯片退出扩展功能模式,它将处于正常运行模式,并准备进入配置模式。
至此,了解了上述内容我们便可以开始着手并去验证这个访问机制了。
-
参照UEFI学习(二),我们创建一个edkii的标准应用工程采用MdeModulePkg包来编译,文件夹名字取为mySio,包含mySio.c和mySio.inf;
-
mySio.c中加入以下内容:
#include <Uefi.h> #include <Library/UefiLib.h> #include <Library/UefiApplicationEntryPoint.h> #include <Library/IoLib.h> //edkii中已包含了对io地址读写的库IoLib.h,从而我们可以使用IoWrite8/IoRead8进行写操作和读操作, //具体函数定义还需跳转到IoLib.h去查看。 #define IoIndexPort 0x2E #define IoDataPort 0x2F EFI_STATUS EFIAPI UefiMain ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { UINT8 ID_High=0; UINT8 ID_Low=0; //进入扩展模式 IoWrite8(IoIndexPort,0x87); IoWrite8(IoIndexPort,0x87); //读取全局寄存器读取ID号:高位+地位 IoWrite8(IoIndexPort,0x20); ID_High=IoRead8(IoDataPort); IoWrite8(IoIndexPort,0x21); ID_Low=IoRead8(IoDataPort); Print(L"SuperIO ID is %X %X\n",ID_High,ID_Low); //退出扩展模式 IoWrite8(IoIndexPort,0xAA); return EFI_SUCCESS; } -
mySio.inf中加入以下内容:
[Defines] INF_VERSION = 0x00010005 BASE_NAME = mySio FILE_GUID = c3d8fe10-29fb-48bd-bf61-6fda693d7712 MODULE_TYPE = UEFI_APPLICATION VERSION_STRING = 1.0 ENTRY_POINT = UefiMain [Sources] mySio.c [Packages] MdePkg/MdePkg.dec MdeModulePkg/MdeModulePkg.dec [LibraryClasses] UefiApplicationEntryPoint UefiLib IoLib -
IoLib.h的路径在edk2\MdePkg\Include\Library\IoLib.h,其中我们可以从中查看到IoWrite8/IoRead8两个函数的描述而对于其底层实现,我们现在可以暂时不用去深入了解:
/** Reads an 8-bit I/O port. Reads the 8-bit I/O port specified by Port. The 8-bit read value is returned. This function must guarantee that all I/O read and write operations are serialized. If 8-bit I/O port operations are not supported, then ASSERT(). @param Port The I/O port to read. @return The value read. **/ UINT8 EFIAPI IoRead8 ( IN UINTN Port ); /** Writes an 8-bit I/O port. Writes the 8-bit I/O port specified by Port with the value specified by Value and returns Value. This function must guarantee that all I/O read and write operations are serialized. If 8-bit I/O port operations are not supported, then ASSERT(). @param Port The I/O port to write. @param Value The value to write to the I/O port. @return The value written the I/O port. **/ UINT8 EFIAPI IoWrite8 ( IN UINTN Port, IN UINT8 Value );
5.按照UEFI学习(二)将我们编写好的工程文件加入到MdeModulePkg的.dsc文件中并进行编译得到mySio.efi,将其转移到具备SHELL环境的u盘中,并在带有NCT5581D的测试机台上开机并进入shell环境,运行我们编译得到的mySio.efi文件。结果如下图所示,可以看到高八位的确如datasheet中描述的一样为default值D4h而低八位应该是因为版本更变。

目标一实现
至此,已经完成了我们的访问机制的验证。接下来我们可以开始去着手实现目标一了。对于参数的传入,我们可以通过创建shell工程应用去实现,通过学习《UEFI原理与编程》第三章的3.2.1,学习shell应用程序的特点与创建过程。接下来给大家分享一下个人对于目标一任务的实现并会在代码中作出一定解释帮助阅读者阅读。
SIO.c
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/DebugLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/IoLib.h>
#include <Library/ShellLib.h>
#include <Library/BaseLib/BaseLibInternals.h>
/*
ShellCEntryLib.h和UefiBootServicesTableLib.h是shell应用工程需要,IoLib.h是IO读取需要,
ShellLib.h是调用ShellPrintEx所需要包含的库,用于指定坐标打印。
*/
#define IoIndexPort 0x2E
#define IoDataPort 0x2F
UINT8 symbol2=0;
UINT8 ROWNUM[16] = {0x00,0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0};
void StartToReadRC()//进入扩展模式
{
IoWrite8(IoIndexPort, 0x87);
IoWrite8(IoIndexPort, 0x87);
return;
}
void QuitRCReader()//推出扩展模式
{
IoWrite8(IoIndexPort, 0xAA);
return;
}
UINT8 SelectLogicalDevice(IN CHAR16* LDn)//选择逻辑设备并屏蔽部分保

最低0.47元/天 解锁文章
1578

被折叠的 条评论
为什么被折叠?



