首页
社区
课程
招聘
[原创]ACPI.sys,从Windows到Bios的桥梁(2):Windows应用程序响应主板上GPIO(SCI)设备中断 软件篇
2021-3-4 21:36 12234

[原创]ACPI.sys,从Windows到Bios的桥梁(2):Windows应用程序响应主板上GPIO(SCI)设备中断 软件篇

2021-3-4 21:36
12234

引言

在上一篇<ACPI.sys,从Windows到Bios的桥梁(2):Windows应用程序响应主板上GPIO(SCI)设备中断 硬件篇>只完成了2件事:

1.Bios接收GPIO SCI事件;


2.Bios转发SCI事件到ACPI命名空间\_GPE.Method(_L56),然后打印日志。


本文将处理从硬件层移至软件层,完成下列2步:


1.Method(_L56)通知ACPI.sys;


2.驱动接收及处理ACPI Notification(MSDN将这类来自BIOS的SCI事件称为ACPI Notification)。以下是实现该目标的步骤:

Step 1.创建承上启下的虚拟设备

设备驱动程序必然依存于某类设备(DeviceNode),这类设备或真实存在,或虚拟创建。虽然本段的标题写的是创建虚拟设备,但该虚拟设备不同于Winddk Sample中Toast创建的设备----Toast通过IoCreateDevice创建,而是由Bios在ACPI命名空间中创建的设备,希望读者加以区分。

那么这样的虚拟设备该怎么创建?ACPI Spec 5.4节有现成可用的例子!很多OEM厂商为了实现硬件增值功能就会创建这种虚拟设备,比如联想(联想打钱!)ThinkPad的"Lenovo PM Device"就属于此。我们可以借助RW utility查看它的实现:

嗯,大家可以参考ThinkPad,依葫芦画瓢可以写出这样的ASL code,编译进Bios开机过后,就能在设备管理器里找到带黄标的"Unknown Device":

Scope(\_SB) {
    Device(HAND) { #HAND->Han's device
        Name(_HID, EISAID("HAN0000"))
 
        Method(_STA) {
                Return(0x0B)
        }
    }
}

查看其Hardware ID,和ASL Code中_HID对象指定的值一致:


Step 2.设计测试驱动,解决黄标

到这一步,已经进入到读者熟悉的软件领域,但是工作量仍然不小。


首先来解决黄标问题。根据设备管理器信息可知黄标是由于Bios虚拟的设备没有安装驱动,所以这一步我们用WinDDK sample----Toast来解决黄标问题!我的测试系统是Win10,安装驱动涉及到2个不起眼的方面:


1).需要为驱动包生成.cat文件;


2).为sys/cat签名。没有这2步,驱动没法安装。

2.1.修改Inf文件,使其匹配Bios虚拟的设备

我假设读者熟悉Wdm驱动,所以直接给出能解决黄标问题的inf文件内容:

[Version]
Signature="$CHICAGO$"
Class=System
ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318}
Provider=%TOS%
CatalogFile=demo.cat
DriverVer=02/23/2021,2.2.0.0
 
[DestinationDirs]
demoNT.CopyFiles = 12
 
[Manufacturer]
%HAN%=Demo,NTx86,NTia64,NTamd64
 
[ControlFlags]
ExcludeFromSelect = *HAN0000
 
[Demo.NTx86]
%*HAN0000.DeviceDescNTx86%=demo,*HAN0000
 
[Demo.NTia64]
%*HAN0000.DeviceDescNTia64%=demo,*HAN0000
 
[Demo.NTamd64]
%*HAN0000.DeviceDescNTamd64%=demo,*HAN0000
 
[SourceDisksNames]
1=%demo%
 
[SourceDisksFiles]
demo.SYS = 1
 
[demo.NT]
CopyFiles=demoNT.CopyFiles
 
[demo.NT.Services]
AddService=demo,2,demo_Service_Inst
 
[demo_Service_Inst]
DisplayName    = %demo.SvcDesc%
ServiceType    = 1
StartType      = 0
ErrorControl   = 1
ServiceBinary  = %12%\demo.SYS
 
[demoNT.CopyFiles]
demo.SYS
 
 
[strings]
HAN                        = "HAN Co., Ltd."
demo                      = "demo"
*HAN0000.DeviceDescNTx86   = "Demo x86 ACPI-Compliant Value Added Logical and General Purpose Device"
*HAN0000.DeviceDescNTia64  = "Demo ia64 ACPI-Compliant Value Added Logical and General Purpose Device"
*HAN0000.DeviceDescNTamd64 = "Demo x64 ACPI-Compliant Value Added Logical and General Purpose Device"
demo.SvcDesc              = "Demo ACPI-Based Value Added Logical and General Purpose Device Driver"

2.2.根据Inf文件生成.cat

Inf文件指定要使用的.cat文件为:demo.cat,参考我的这篇博文:为sys/cat文件生成测试签名,即可生成demo.cat

Inf2cat.exe /driver:E:\WinDDk\my_test\objchk_win7_amd64\amd64\ /os:7_x64

2.3.为.cat/.sys签名

参考我的这篇博文:为sys/cat文件生成测试签名,可以生成证书文件并为sys/cat签名:

Signtool sign /a /v /s PrivateCertStore /n Contoso.com(Test) demo.cat
Signtool sign /a /v /s PrivateCertStore /n Contoso.com(Test) demo.sys

命令执行后,在.cat/.sys文件的文件属性页就有"Digital Signatures"标签页:

完成以上步骤,并在OS下开启Test Mode,之后在设备管理器里对"Unknow Device" 安装驱动程序,即可解决黄标问题:

ACPI\HAN0000从Other Device类移到了System Device类,另外,设备描述名也从Unknown Device变为Inf文件中DeviceDesc节指定的字符串:


3.编写驱动,接受ACPI Notification

这一节要分2部分:

1.虚拟设备HAN0000中转SCI到ACPI.sys;

2.驱动程序 处理ACPI Notify。


3.1.SCI的中转:进一步完善Bios Method(_L56)的功能,转发SCI到驱动层。

如开头所说,前一篇仅在Method(_L56)中打印日志,仅如此,OS无法获知来自PCH的SCI。参考ACPI Spec 5.6 ACPI Event Programming Model,ASL code还需要执行Notify语句,通知其他设备:


还是以ThinkPad的"Lenovo PM Device"为例,当设备(如EC)发现外部事件触发,同时又需要OS协同配合,此时设备会借道"Lenovo PM Device"这个Bios虚拟设备传话到OS(我怎么觉得它更像是阴阳界的灵媒?)


参考联想的做法,我可以在\_GPE.Method(_L56)中加入下列代码,从而将PCH SCI事件转交给\_SB.HAND。至于\_SB.HAND要做什么?它什么都不用做,躺平就行。因为它属于ACPI驱动,如果Bios层不做处理,驱动程序必须接管处理(官僚主义作风?),这不就是将控制权转移到软件层了?

External(\_SB.HAND, MethodObj)  
  Method (_L56, 0, Serialized){
    DSTR("l-event _L56");
    Notify(\_SB.HAND,0x9A)
  }

3.2.SCI的处理:完成ACPI驱动

ACPI驱动估计只有OEM厂商的驱动工程师会涉及,所以接下来的内容可能对大家有点陌生,但是我们背靠微软的ACPI driver Spec并结合WinDDK Toast,就能使工作量大大减轻。受限于篇幅,我仅列出用于实现接受ACPI Notification功能的代码。


3.2.1.获得ACPI direct call Interface接口并注册AcpiNotify:

NTSTATUS
DemoAddDevice(__in PDRIVER_OBJECT DriverObject,
			   __in PDEVICE_OBJECT PhysicalDeviceObject
				)
{
    //Phase 1
    PDEVICE_OBJECT	fdo = NULL;
	PDEVICE_OBJECT	lowerDevice = NULL;
	PDemoDATA		demoData;
    
    RtlInitUnicodeString(&NameString, L"\\Device\\demo");
	status = IoCreateDevice (
					DriverObject,			// our driver object
					sizeof (DEMODATA),		// device object extension size
					&NameString,			// FDOs do not have names
					FILE_DEVICE_UNKNOWN,
					0,						// No special characteristics
					FALSE,
					&fdo);
 
    lowerDevice = IoAttachDeviceToDeviceStack(fdo,PhysicalDeviceObject);
    demoData= fdo->DeviceExtension;
	demoData->DeviceObject = fdo;
	demoData->LowerDeviceObject = lowerDevice;
	demoData->Pdo = PhysicalDeviceObject;
 
    //Phase 2
    status = GetAcpiInterfaces(demoData);
    RegisterNotifyHandler(demoData);
    KeInitializeSpinLock(&demoData->RetrieveLock);
 
    //Phase 3
    RtlInitUnicodeString(&DOSNameString, L"\\DosDevices\\demo");
	status = IoCreateSymbolicLink(&DOSNameString, &NameString);
    fdo->Flags &= ~DO_DEVICE_INITIALIZING;
	fdo->Flags |= DO_POWER_PAGABLE;
}

代码片中的Phase 1和Phase 3是常规的Wdm驱动框架;Phase 2则是获得ACPI direct call Interface并注册Notify函数。在微软的ACPI driver Spec<ACPI Driver Interface in Windows Vista>中对ACPI direct call Interface有详尽的介绍:

GetAcpiInterfaces用于获得ACPI direct call Interface:

extern ACPI_INTERFACE_STANDARD	AcpiInterfaces;
 
NTSTATUS
GetAcpiInterfaces(IN DemoDATA demoData)
{
	KEVENT              event;
	IO_STATUS_BLOCK     ioStatus;
	NTSTATUS            status;
	PIRP                Irp;
	PIO_STACK_LOCATION  irpSp;
 
	KeInitializeEvent(&event, SynchronizationEvent, FALSE);
 
	Irp = IoBuildSynchronousFsdRequest(
		IRP_MJ_PNP,
		demoData->LowerDeviceObject,
		NULL,
		0,
		NULL,
		&event,
		&ioStatus
		);
 
	if (!Irp) {
		return(STATUS_INSUFFICIENT_RESOURCES);
	}
 
	Irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
	Irp->IoStatus.Information = 0;
 
 
	irpSp = IoGetNextIrpStackLocation(Irp);
 
	irpSp->MajorFunction = IRP_MJ_PNP;
	irpSp->MinorFunction = IRP_MN_QUERY_INTERFACE;
	irpSp->Parameters.QueryInterface.InterfaceType          = (LPGUID) &GUID_ACPI_INTERFACE_STANDARD;
	irpSp->Parameters.QueryInterface.Version                = 1;
	irpSp->Parameters.QueryInterface.Size                   = sizeof (AcpiInterfaces);
	irpSp->Parameters.QueryInterface.Interface              = (PINTERFACE) &AcpiInterfaces;
	irpSp->Parameters.QueryInterface.InterfaceSpecificData  = NULL;
 
	//
	// send the request down
	//
	status = IoCallDriver(demoData->LowerDeviceObject, Irp);
 
	return status;
}

AddDevice函数用返回的ACPI direct call Interface(存放在全局变量 ACPI_INTERFACE_STANDARD AcpiInterfaces中),调用RegisterNotifyHandler来注册ACPI Notification回调函数demoNotifyHandler :

NTSTATUS
RegisterNotifyHandler(IN PDemoData demoData)
{
	NTSTATUS	status;
 
	status = AcpiInterfaces.RegisterForDeviceNotifications(
				demoData->Pdo,
				demoNotifyHandler,
				AcpiInterfaces.Context
				);
 
 
	return status;
}

3.2.2.demoNotifyHandler处理来自Acpi层的Event

一般应用程序才是处理事件的终端,它会以异步等待的方式调用DeviceIoControl等待硬件SCI事件发生:

#define TVALDDRVR_DEVICE_OPEN_NAME   TEXT("\\\\.\\demo")
 
	hDriver = ::CreateFile(TVALZ_DEVICE_OPEN_NAME,
							GENERIC_READ,
							FILE_SHARE_READ,
							NULL,
							OPEN_EXISTING,
							FILE_ATTRIBUTE_NORMAL |
							FILE_FLAG_OVERLAPPED ,
							NULL);
 
::DeviceIoControl(m_hDriver, 
    				IOCTL_TVALZ_RETRIEVE_EVENT,
					&iaeInputBuffer, 
					sizeof(iaeInputBuffer),
					&iaeOutputBuffer, 
					sizeof(iaeOutputBuffer),
					&dwReturned, &ol);
if (::GetLastError() == ERROR_IO_PENDING)
{
    dwResult = ::WaitForMultipleObjects(2, hEventArray, FALSE, INFINITE) - WAIT_OBJECT_0;
}

1).当事件没有触发时,IRP进入驱动自定义的IRP Pending队列中。对应的应用程序则阻塞等待;

2).当ACPI Manager接收到来自Bios的SCI事件后,则调用注册的回调函数。此时,驱动程序遍历整个队列,比较队列中Pending IRP!AssociatedIrp.SystemBuffer的值是否和NotifyCode的值相同,如果相同则完成IRP。对应的应用程序被唤醒,进行下一轮Wait/Wake:

#define GPIO_QEVENT_L56			                  0x9a //\_GPE._L56 发出的Notify Code的值为0x9A 
 
VOID
demoNotifyHandler(IN PVOID Context,
				   IN ULONG ulNotifyValue
				  )
{
	KIRQL				Irql;
	PULONG				pNotification;
	PLIST_RETRIEVE_IRP	pEntry, pNextEntry;
	PIOBUF_ACPI_EVENT	pAcpiEvent;
 
	UINT64             MsrData64 = 0; //AddNewEvent+
	WORK_QUEUE_ITEM*   turboWorkItem; //AddNewEvent+
	PIRP			   retrieveIrp;
 
    DebugPrint(-1,(" :     NotifyHandler...Notification=%x\n", ulNotifyValue));
 
		KeAcquireSpinLock(&RetrieveLock, &Irql);
		if (!IsListEmpty((PLIST_ENTRY)&RetrieveList)) {
			for (pEntry = (PLIST_RETRIEVE_IRP)RetrieveList.ListEntry.Flink; pEntry != &RetrieveList; pEntry = pNextEntry) {
				pNextEntry = (PLIST_RETRIEVE_IRP)pEntry->ListEntry.Flink;
				pAcpiEvent = (PIOBUF_ACPI_EVENT)pEntry->pIrp->AssociatedIrp.SystemBuffer;
 
				if (pAcpiEvent->ulNotifyCode == ulNotifyValue) {
					RemoveEntryList((PLIST_ENTRY)pEntry);
					InsertTailList((PLIST_ENTRY)&CompleteList, (PLIST_ENTRY)pEntry);
 
 
					if(ulNotifyValue == GPIO_QEVENT_L56)
					{
						DebugInfo(1,("Demo:receive Q-event _L56\n"));
					}
					else
					{}
//tvalz_AddNewEvent+					
				}
			}
		}
		KeReleaseSpinLock(&RetrieveLock, Irql);
 
		if (!IsListEmpty((PLIST_ENTRY)&CompleteList)) {
			for (pEntry = (PLIST_RETRIEVE_IRP)CompleteList.ListEntry.Flink; pEntry != &CompleteList; pEntry = pNextEntry) {
				pNextEntry = (PLIST_RETRIEVE_IRP)pEntry->ListEntry.Flink;
				RemoveEntryList((PLIST_ENTRY)pEntry);
				IoSetCancelRoutine(pEntry->pIrp, NULL);
 
				pAcpiEvent = (PIOBUF_ACPI_EVENT)pEntry->pIrp->AssociatedIrp.SystemBuffer;
				pAcpiEvent->dwStatus = STATUS_SUCCESS;
				pEntry->pIrp->IoStatus.Information = sizeof(PIOBUF_ACPI_EVENT);
				pEntry->pIrp->IoStatus.Status = STATUS_SUCCESS;
				IoCompleteRequest(pEntry->pIrp, IO_NO_INCREMENT);
 
			}
		}
 
	TvalzDebug(Tvalz_INFO,("TVALZ : NotifyHandler...end\n"));
}

由于应用程序等待的NotifyCode就是Bios code中Method (_L56) Notify语句抛出的值,因此只要GPIO引脚没接地,回调函数会一直被调用,因此DbgView界面上会输出大量的日志

附注:为了Enable DbgView输出:

1).在注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager下创建DWORD子项"Debug Print Filter",设置其值为0xF;

2).在DbgView中勾选"Enable Verbose Kernel Output"



结尾

本文完。还有后续文章吗?当然有!驱动程序有部分工作是处理电源\Pnp事件,这些也需要Bios协同配合,所以下一篇是Bios和驱动如何处理电源事件~请期待



[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞14
打赏
分享
最新回复 (21)
雪    币: 12040
活跃值: (15364)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
pureGavin 2 2021-3-4 23:15
2
0
感谢分享
雪    币: 3350
活跃值: (3372)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
fengyunabc 1 2021-3-4 23:21
3
0
666,感谢分享!
雪    币: 4889
活跃值: (2265)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lionnnn 2021-3-5 08:45
4
0
这么底层全面的文章,少见
雪    币: 1243
活跃值: (1815)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
库尔 2021-3-5 15:45
5
0
感谢分享
雪    币: 12
活跃值: (220)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
水上心 2021-3-19 16:37
6
0
说到关键处 最精彩的部分一笔带过了?
register operation region?
雪    币: 802
活跃值: (4423)
能力值: ( LV12,RANK:260 )
在线值:
发帖
回帖
粉丝
hyjxiaobia 2 2021-3-19 19:06
7
0
水上心 说到关键处 最精彩的部分一笔带过了? register operation region?
Register operation region指哪块?我看看能不能补上
雪    币: 12
活跃值: (220)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
水上心 2021-3-19 20:24
8
0
好的,多谢,
雪    币: 12
活跃值: (220)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
水上心 2021-3-19 20:24
9
0
https://docs.microsoft.com/en-us/windows-hardware/drivers/acpi/operation-of-an-acpi-device-function-driver
雪    币: 802
活跃值: (4423)
能力值: ( LV12,RANK:260 )
在线值:
发帖
回帖
粉丝
hyjxiaobia 2 2021-3-19 20:28
10
0
水上心 https://docs.microsoft.com/en-us/windows-hardware/drivers/acpi/operation-of-an-acpi-device-function- ...
确认一下,你是想在驱动中主动调用acpi命名空间中的某个方法,对吧?
雪    币: 12
活跃值: (220)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
水上心 2021-3-19 22:15
11
0
我是想知道operation region(OpRegisterRegionHandler)的用途和应用场景是啥
调用acpi中的method我这里有方法的,可以在r3任意调用acpi namespace中的任意一个method
很感谢你帮我扫盲了sci interrupt、gpe,和devienotification
雪    币: 12
活跃值: (220)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
水上心 2021-3-19 22:22
12
1
acpi.sys deviceiocontrol有好几个方法,除了evalacpimethod之外,还有OpXXXX,会在namespace node device下生成一个operationRegion,是fdo驱动提供的一块内存空间,同时还会注册一个callback,也是fdo驱动提供,在acpi node 设备调用方法时会异步调用fdo的callback 操作这块region,像是fdo在acpi node device下注册了一个小端口,所以也有异步通知的功能,不知道和acpi node device下的deviceNotification callback(RegisterForDeiveNotication向下级pdo挂的acpi 分device object结构中注册的)有啥区别,应用场景上有啥不同
雪    币: 802
活跃值: (4423)
能力值: ( LV12,RANK:260 )
在线值:
发帖
回帖
粉丝
hyjxiaobia 2 2021-3-20 13:09
13
0
水上心 acpi.sys deviceiocontrol有好几个方法,除了evalacpimethod之外,还有OpXXXX,会在namespace node device下生成一个operationRegi ...
我靠,你研究的这么深,我看下
雪    币: 802
活跃值: (4423)
能力值: ( LV12,RANK:260 )
在线值:
发帖
回帖
粉丝
hyjxiaobia 2 2021-3-20 13:16
14
1
我仔细看了一下,你说的operationregion,我们是直接在bios里做好了,所以没我没接触过。我找找显卡相关的,不知道有没有
雪    币: 12
活跃值: (220)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
水上心 2021-3-20 14:08
15
0
 多谢了啊,老哥,这行研究这块的真的凤毛麟角,少之又少,懂得人也很少
你这种文章完全属于精品,收藏和点赞数不相符,帮你加把油了
雪    币: 802
活跃值: (4423)
能力值: ( LV12,RANK:260 )
在线值:
发帖
回帖
粉丝
hyjxiaobia 2 2021-3-20 16:37
16
0
水上心 [em_27] 多谢了啊,老哥,这行研究这块的真的凤毛麟角,少之又少,懂得人也很少 你这种文章完全属于精品,收藏和点赞数不相符,帮你加把油了
那就谢谢了
雪    币: 12
活跃值: (220)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
水上心 2021-3-21 10:56
17
1
感觉opregion是另一种异步数据交互形式,有点像r3层的内存映射,在bios和driver之间共享一段内存,大家都可以访问它,但driver不能主动去修改和写它,必须等着bios有需要的时候,利用drv注册的ophandler去修改它,好想是这个意思


兄弟有空的话,可以翻翻你那边的显卡驱动代码,是不是这么回事,如果我理解的不对,请尽管指正我
雪    币: 108
活跃值: (863)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yangya 2021-4-17 17:58
18
1
Opregion的handler一般是ACPI.sys 注册的,也就是由ACPI.SYS去访问。 ASL code去访问某个Opregion会触发ACPI的里面的Opregion handler.  当然driver也可以自己定义一块私有的区域,然后注册OpregionHandler去处理,但是一般没有必要,直接写ASL code访问就行了,driver里面直接call ALS code method。这样实现起来更简单,所有的同步机制交给ACPI driver去做。
雪    币: 12
活跃值: (220)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
水上心 2021-4-22 14:10
19
0

好的,非常感谢指点

最后于 2021-4-23 09:11 被水上心编辑 ,原因:
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_eeoynudy 2023-8-1 15:02
20
0
所以Driver主动调用 acpi asl code method应该怎样实现,按照ms的acpi method 调用提示,也不蓝屏也看不到执行效果
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_eeoynudy 2023-8-1 15:17
21
0
谁有完整的demo code和示例讲解吗
雪    币: 802
活跃值: (4423)
能力值: ( LV12,RANK:260 )
在线值:
发帖
回帖
粉丝
hyjxiaobia 2 2023-9-19 00:07
22
0
mb_eeoynudy 谁有完整的demo code和示例讲解吗
微软泄露的xp代码里,有电池 ec驱动,这些驱动带有你要的driver调用asl的例子
游客
登录 | 注册 方可回帖
返回