UEFI driver框架
上一节我们讲了服务型驱动,它有如下几个特点:
1. 在Image的入口函数中执行安装。
2. 服务型驱动不需要驱动特定硬件,可以安装到任意controller上。
3. 没有提供卸载函数。
一个真正的驱动程序,在安装时首先要找到对应的硬件设备(在UEFI中是要找到对应的Controller), 然后执行安装操作,将驱动程序安装到硬件设备的controller上。 有时候我们还需要卸载驱动,更新驱动(先卸载旧的驱动,然后安装新的驱动)。 有时候安装操作可能需要执行多次,例如:第一次安装时发现设备没有准备好,或者所依赖的某个Protocol没有安装,就需要退出安装,执行其他操作,然后进行第二次安装。
那么我们可以总结出一个完整的驱动程序框架需要三部分:
1. Findout() 找出对应的硬件设备
2. Install() 安装驱动到指定的硬件设备 或者说 Start() 启动硬件设备
3. Uninstall()从硬件设备中卸载驱动 或者说 Stop() 停止硬件设备。
另外很重要的一点是框架必须支持多次安装。 上一节我们实现的驱动是不能多次安装的(在入口函数中执行安装),如果首次安装失败例如
InstallProtocolInterface(...)返回错误,我们只能unload 驱动文件(image),从新loadImage。
我们来看UEFI驱动框架是如何实现这几点的。
在UEFI驱动的入口函数中,安装EFI Driver Binding Protocol到某个Handle(大部分情况下是自身即ImageHandle, 有时也会安装到其它Handle上), 这个Driver Binding Protocol实例会常驻内存,用于驱动的安装和卸载。使用DriverBindingProtocol使得我们可以多次操作(查找设备,安装卸载)驱动.
在Driver Binding Protocol中实现了框架的三个部分的接口。下面是DriverBindingProtocol的声明:
//
/// This protocol provides the services required to determine if a driver supports a given controller.
/// If a controller is supported, then it also provides routines to start and stop the controller.
///
struct _EFI_DRIVER_BINDING_PROTOCOL {
EFI_DRIVER_BINDING_SUPPORTED Supported;
EFI_DRIVER_BINDING_START Start;
EFI_DRIVER_BINDING_STOP Stop;
///
/// The version number of the UEFI driver that produced the
/// EFI_DRIVER_BINDING_PROTOCOL. This field is used by
/// the EFI boot service ConnectController() to determine
/// the order that driver's Supported() service will be used when
/// a controller needs to be started. EFI Driver Binding Protocol
/// instances with higher Version values will be used before ones
/// with lower Version values. The Version values of 0x0-
/// 0x0f and 0xfffffff0-0xffffffff are reserved for
/// platform/OEM specific drivers. The Version values of 0x10-
/// 0xffffffef are reserved for IHV-developed drivers.
///
UINT32 Version;
///
/// The image handle of the UEFI driver that produced this instance
/// of the EFI_DRIVER_BINDING_PROTOCOL.
///
EFI_HANDLE ImageHandle;
///
/// The handle on which this instance of the
/// EFI_DRIVER_BINDING_PROTOCOL is installed. In most
/// cases, this is the same handle as ImageHandle. However, for
/// UEFI drivers that produce more than one instance of the
/// EFI_DRIVER_BINDING_PROTOCOL, this value may not be
/// the same as ImageHandle.
///
EFI_HANDLE DriverBindingHandle;
};
核心是Support,Start,Stop三个成员函数,对应我们总结的三个部分。
首先看Support函数,简单来讲,如果ControllerHandle是我们要找的Controoler,该函数返回EFI_SUCCESS, 否则返回EFI_UNSUPPORTED、 EFI_ACCESS_DENIED或EFI_ALREADY_STARTED等等。
typedef
EFI_STATUS
(EFIAPI *EFI_DRIVER_BINDING_PROTOCOL_SUPPORTED) (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL // Device driver 忽略该参数,
//Bus Driver(例如MdeModulePkg/Bus/Pci/PciBusDxe/PciBus.c)才会使用该参数。
);
再看Start函数,Start()用来启动硬件设备,在函数中最重要的事情是调用InstallProtocolInterface()或者InstallMultipleProtocolInterfaces()在
ControllerHandle上安装驱动Protocol。
typedef
EFI_STATUS
(EFIAPI *EFI_DRIVER_BINDING_PROTOCOL_START) (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
);
Stop函数,用于卸载驱动(调用UninstallProtocolInterface()或UninstallMultipleProtocolInterfaces()从
ControllerHandle卸载驱动协议),并停止硬件设备。
typedef
EFI_STATUS
(EFIAPI *EFI_DRIVER_BINDING_PROTOCOL_STOP) (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN UINTN NumberOfChildren,
IN EFI_HANDLE *ChildHandleBuffer OPTIONAL
);
对Device Driver来讲,
NumberOfChildren 为0,
ChildHandleBuffer 为NULL。 对Bus Driver来讲,如果NumberOfChildren不为零,那么ChildHandleBuffer中的子节点都要被释放。
我们分别研究了驱动框架的三个部分,这三个部分是如何联系起来的呢?看下面的代码(MdeModulePkg/Core/Dxe/Hand/DriverSupport.c:CoreConnectSingleController),可以大致了解UEFI驱动程序框架是如何工作的。
do {
//
// Loop through the sorted Driver Binding Protocol Instances in order, and see if
// any of the Driver Binding Protocols support the controller specified by
// ControllerHandle.
//
DriverBinding = NULL;
DriverFound = FALSE;
for (Index = 0; (Index < NumberOfSortedDriverBindingProtocols) && !DriverFound; Index++) {
if (SortedDriverBindingProtocols[Index] != NULL) {
DriverBinding = SortedDriverBindingProtocols[Index];
PERF_START (DriverBinding->DriverBindingHandle, "DB:Support:", NULL, 0);
Status = DriverBinding->Supported(
DriverBinding,
ControllerHandle,
RemainingDevicePath
);
PERF_END (DriverBinding->DriverBindingHandle, "DB:Support:", NULL, 0);
if (!EFI_ERROR (Status)) {
SortedDriverBindingProtocols[Index] = NULL;
DriverFound = TRUE;
//
// A driver was found that supports ControllerHandle, so attempt to start the driver
// on ControllerHandle.
//
PERF_START (DriverBinding->DriverBindingHandle, "DB:Start:", NULL, 0);
Status = DriverBinding->Start (
DriverBinding,
ControllerHandle,
RemainingDevicePath
);
PERF_END (DriverBinding->DriverBindingHandle, "DB:Start:", NULL, 0);
if (!EFI_ERROR (Status)) {
//
// The driver was successfully started on ControllerHandle, so set a flag
//
OneStarted = TRUE;
}
}
}
}
} while (DriverFound);
CoreConnectSingleController(IN EFI_HANDLE ControllerHandle, IN EFI_HANDLE *ContextDriverImageHandles OPTIONAL, IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL)用于为ControllerHandle安装驱动。如果 ContextDriverImageHandles为空, 则遍历系统中的所有DriverBindingProtocol,否则就只遍历指定的DriverBindingProtocol。SortedDriverBindingProtocols[]存放了需要测试的DriverBindingProtocol, 对于每一个需要测试的DriverBindingProtocol,首先调用DriverBinding->Supported(...)测试该DriverBindingProtocol是否支持ControllerHandle, 如果Supported函数返回EFI_SUCCESS,则调用DriverBinding->Start(...)向ControllerHandle安装驱动,启动设备。
CoreDisconnectController(
IN EFI_HANDLE ControllerHandle,
IN EFI_HANDLE DriverImageHandle OPTIONAL,
IN EFI_HANDLE ChildHandle OPTIONAL
) 用于卸载掉ControllerHandle上指定的驱动(若DriverImageHandle 则卸载掉ControllerHandle上的所有驱动)。
看MdeModulePkg/Core/Dxe/Hand/DriverSupport.c:876
if (ChildHandle == NULL || ChildHandleValid) {
ChildrenToStop = 0;
Status = EFI_SUCCESS;
if (ChildBufferCount > 0) {
if (ChildHandle != NULL) {
ChildrenToStop = 1;
Status = DriverBinding->Stop (DriverBinding, ControllerHandle, ChildrenToStop, &ChildHandle);
} else {
ChildrenToStop = ChildBufferCount;
Status = DriverBinding->Stop (DriverBinding, ControllerHandle, ChildrenToStop, ChildBuffer);
}
}
if (!EFI_ERROR (Status) && ((ChildHandle == NULL) || (ChildBufferCount == ChildrenToStop))) {
Status = DriverBinding->Stop (DriverBinding, ControllerHandle, 0, NULL);
}
if (!EFI_ERROR (Status)) {
StopCount++;
}
}
了解了各个部分的细节后,我们在看一遍加载Driver的整个过程。
首先在Shell中使用命令Load 将Driver文件加载到内存, Load后UEFI会调用gBS->StartImage(...) 执行DriverImage的入口函数, 在入口函数里Driver Binding Protocol被加载到Handle上(Driver Image handle 或者其它的Controller handle),然后UEFI会遍历所有的Controller,调用Driver Binding Protocol的Supported 函数测试这个Driver是否支持该Controller,如果支持则调用Start()安装驱动。
EFI Component Name Protocol
编写Driver
驱动分为两部分,一部分是硬件相关的部分,这部分是驱动的内容,用于驱动硬件设备,为用户提供服务,以协议的形式出现,例如DiskIo,BlockIo。 另一部分驱动的框架部分,需要实现Driver Binding Protocol,主要是其三个接口(Supported, Start, Stop),这部分用于驱动的安装与卸载。硬件相关部分不是本节讲述的重点。
本节主要讲述Device Driver如何实现框架部分。
Spec中详细描述了如何编写Supported, Start, Stop 三个函数,下面的斜体内容翻译至Spec2.3
Supported函数要点(Spec2.3:339)
1. 忽略参数 RemainingDevicePath
2. 使用函数OpenProtocol()打开所有需要的Protocols。 标准的驱动要用EFI_OPEN_PROTOCOL_BY_DRIVER属性打开Protocol。 如果要独占某个Protocol,首先要关闭所有使用该Protocol的其它驱动,然后用属性EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE打开Protocol。
3. 如果(2)中OpenProtocol()返回错误,调用CloseProtcol()关闭所有已经打开的Protocol并返回错误代码。
4. 所需的所有Protocols成功打开,则测试这个Driver是否支持此Controller。有时使用这些Protocols足以完成测试,有时还需要此Controller的其它特征。如果任意一项测试失败,则用CloseProtocol()关闭所有打开的Protocol,返回EFI_UNSUPPORTED.
5. 测试成功,调用CloseProtocol()关闭所有已经打开的Protocols。
6. 返回 EFI_SUCCESS。
Start函数要点(spec2.3:345)
1. 忽略参数 RemainingDevicePath
2. 使用函数OpenProtocol()打开所有需要的Protocols。 标准的驱动要用EFI_OPEN_PROTOCOL_BY_DRIVER属性打开Protocol。 如果要独占某个Protocol,首先要关闭所有使用该Protocol的其它驱动,然后用属性EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE打开Protocol。
3. 如果(2)中OpenProtocol()返回错误,调用CloseProtcol()关闭所有已经打开的Protocol并返回错误代码。
4. 初始化ControllerHandle所指的设备。如果有错误,则关闭所有已打开的Protocols并返回 EFI_DEVICE_ERROR。
5. 分配并初始化要用到的数据结构,这些数据结构包括驱动Protocols及其它相关的私有数据结构。如果分配资源时发生错误,则关闭所有已打开的Protocols,释放已经得到的资源,返回EFI_OUT_OF_RESOURCES。
6. 用InstallMultipleProtocolInterfaces()安装驱动协议到ControllerHandle。如果有错误发生,则关闭所有已打开的Protocols,并返回错误代码。
7. 返回EFI_SUCESS。
Stop函数要点(spec2.3:)
1. 用UninstallMultipleProtocolInterfaces() Uninstall 所安装的Protocols。
2. 关闭所有已打开的Protocols。
3. 释放所有已申请的资源。
下面我们以AC97 控制器驱动为例,介绍如何编写Device driver。南桥芯片中集成的AC97控制器是一种PCI设备,在开始写驱动之前让我们补充一点PCI设备驱动及AC97声卡的背景知识。
首先让我们建立实验环境, 在QEMU中选择开启 Audio ,类型选择为Intel AC97.
PCI设备及PciIo
每个PCI设备都有三种地址空间:配置空间,IO空间和内存空间。
系统初始化时系统会初始化每个PCI设备的配置空间寄存器。配置地址空间大小为256字节,前64字节是标准的,后面的寄存器由设备自定义用途。
|
0x0
|
0x1
|
0x2
|
0x3
|
0x4
|
0x5
|
0x6
|
0x7
|
0x8
|
0x9
|
0xa
|
0xb
|
0xc
|
0xd
|
0xe
|
0xf
|
0x00
|
Vendor ID
|
Device ID
|
Command Reg.
|
Status Reg.
|
Revision ID
|
Class Code
|
Cache
Line
|
Latency
Timer
|
Header
Type
|
BIST
|
0x10
|
Base Address 0
|
Base Address 1
|
Base Address 2
|
Base Address 3
|
0x20
|
Base Address 4
|
Base Address 5
|
Card Bus CIS pointer
|
Subsytem Vendor ID
|
Subsystem Device ID
|
0x30
|
Expansion ROM Base Address
|
|
|
|
|
|
|
|
|
IRQ Line
|
IRQ Pin
|
Min_Gnt
|
Max_lat
|
例如Qemu中AC97的配置空间内容如下
PciRoot(0x0)/Pci(0x4,0x0)
UINT16 VendorId :8086
UINT16 DeviceId :2415
UINT16 Command :7
UINT16 Status :280
UINT8 RevisionID : 1
UINT8 ClassCode[2] : 4
UINT8 ClassCode[1] : 1
UINT8 ClassCode[0] : 0
UINT8 CacheLineSize :0
UINT8 LatencyTimer : 0
UINT8 HeaderType : 0
UINT8 BIST : 0
Bar[0] : C001
Bar[1] : C401
Bar[2] : 0
Bar[3] : 0
Bar[4] : 0
Bar[5] : 0
PCI设备中的IO和内存空间被划分为1~6个互补重叠的子空间,每个子空间用于完成一组相对独立的子功能。Base Address 0 ~5 表示子空间的基地址(物理地址)。
对设备的操作主要是通过对子空间的读写来实现的。 UEFI提供了EFI_PCI_IO_PROTOCOL来操作PCI设备。
///
/// The EFI_PCI_IO_PROTOCOL provides the basic Memory, I/O, PCI configuration,
/// and DMA interfaces used to abstract accesses to PCI controllers.
/// There is one EFI_PCI_IO_PROTOCOL instance for each PCI controller on a PCI bus.
/// A device driver that wishes to manage a PCI controller in a system will have to
/// retrieve the EFI_PCI_IO_PROTOCOL instance that is associated with the PCI controller.
///
struct _EFI_PCI_IO_PROTOCOL {
EFI_PCI_IO_PROTOCOL_POLL_IO_MEM PollMem;
EFI_PCI_IO_PROTOCOL_POLL_IO_MEM PollIo;
EFI_PCI_IO_PROTOCOL_ACCESS Mem;
EFI_PCI_IO_PROTOCOL_ACCESS Io;
EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Pci;
EFI_PCI_IO_PROTOCOL_COPY_MEM CopyMem;
EFI_PCI_IO_PROTOCOL_MAP Map;
EFI_PCI_IO_PROTOCOL_UNMAP Unmap;
EFI_PCI_IO_PROTOCOL_ALLOCATE_BUFFER AllocateBuffer;
EFI_PCI_IO_PROTOCOL_FREE_BUFFER FreeBuffer;
EFI_PCI_IO_PROTOCOL_FLUSH Flush;
EFI_PCI_IO_PROTOCOL_GET_LOCATION GetLocation;
EFI_PCI_IO_PROTOCOL_ATTRIBUTES Attributes;
EFI_PCI_IO_PROTOCOL_GET_BAR_ATTRIBUTES GetBarAttributes;
EFI_PCI_IO_PROTOCOL_SET_BAR_ATTRIBUTES SetBarAttributes;
///
/// The size, in bytes, of the ROM image.
///
UINT64 RomSize;
///
/// A pointer to the in memory copy of the ROM image. The PCI Bus Driver is responsible
/// for allocating memory for the ROM image, and copying the contents of the ROM to memory.
/// The contents of this buffer are either from the PCI option ROM that can be accessed
/// through the ROM BAR of the PCI controller, or it is from a platform-specific location.
/// The Attributes() function can be used to determine from which of these two sources
/// the RomImage buffer was initialized.
///
VOID *RomImage;
};
今天我们只用到
EFI_PCI_IO_PROTOCOL_ACCESS Io; 和 EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Pci; 这两个子功能,
EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS 用于读写配置空间,
EFI_PCI_IO_PROTOCOL_ACCESS用于读写Io空间。
EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS :
/**
Enable a PCI driver to access PCI controller registers in PCI configuration space.
@param This A pointer to the EFI_PCI_IO_PROTOCOL instance.
@param Width Signifies the width of the memory operations.
@param Offset The offset within the PCI configuration space for the PCI controller.
@param Count The number of PCI configuration operations to perform.
@param Buffer For read operations, the destination buffer to store the results. For write
operations, the source buffer to write data from.
@retval EFI_SUCCESS The data was read from or written to the PCI controller.
@retval EFI_UNSUPPORTED The address range specified by Offset, Width, and Count is not
valid for the PCI configuration header of the PCI controller.
@retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack of resources.
@retval EFI_INVALID_PARAMETER Buffer is NULL or Width is invalid.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_PCI_IO_PROTOCOL_CONFIG)(
IN EFI_PCI_IO_PROTOCOL *This,
IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
IN UINT32 Offset,
IN UINTN Count,
IN OUT VOID *Buffer
);
typedef struct {
///
/// Read PCI controller registers in PCI configuration space.
///
EFI_PCI_IO_PROTOCOL_CONFIG Read;
///
/// Write PCI controller registers in PCI configuration space.
///
EFI_PCI_IO_PROTOCOL_CONFIG Write;
} EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS;
EFI_PCI_IO_PROTOCOL_ACCESS:
typedef enum {
EfiPciIoWidthUint8 = 0,
EfiPciIoWidthUint16,
EfiPciIoWidthUint32,
EfiPciIoWidthUint64,
EfiPciIoWidthFifoUint8,
EfiPciIoWidthFifoUint16,
EfiPciIoWidthFifoUint32,
EfiPciIoWidthFifoUint64,
EfiPciIoWidthFillUint8,
EfiPciIoWidthFillUint16,
EfiPciIoWidthFillUint32,
EfiPciIoWidthFillUint64,
EfiPciIoWidthMaximum
} EFI_PCI_IO_PROTOCOL_WIDTH;
/**
Enable a PCI driver to access PCI controller registers in the PCI memory or I/O space.
@param This A pointer to the EFI_PCI_IO_PROTOCOL instance.
@param Width Signifies the width of the memory or I/O operations.
@param BarIndex The BAR index of the standard PCI Configuration header to use as the
base address for the memory or I/O operation to perform.
@param Offset The offset within the selected BAR to start the memory or I/O operation.
@param Count The number of memory or I/O operations to perform.
@param Buffer For read operations, the destination buffer to store the results. For write
operations, the source buffer to write data from.
@retval EFI_SUCCESS The data was read from or written to the PCI controller.
@retval EFI_UNSUPPORTED BarIndex not valid for this PCI controller.
@retval EFI_UNSUPPORTED The address range specified by Offset, Width, and Count is not
valid for the PCI BAR specified by BarIndex.
@retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack of resources.
@retval EFI_INVALID_PARAMETER One or more parameters are invalid.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_PCI_IO_PROTOCOL_IO_MEM)(
IN EFI_PCI_IO_PROTOCOL *This,
IN EFI_PCI_IO_PROTOCOL_WIDTH Width, // 每个元素的大小
IN UINT8 BarIndex, // 0~5中的一个
IN UINT64 Offset, // 偏移
IN UINTN Count, // 元素个数
IN OUT VOID *Buffer
);
typedef
struct {
///
/// Read PCI controller registers in the PCI memory or I/O space.
///
EFI_PCI_IO_PROTOCOL_IO_MEM Read;
///
/// Write PCI controller registers in the PCI memory or I/O space.
///
EFI_PCI_IO_PROTOCOL_IO_MEM Write;
} EFI_PCI_IO_PROTOCOL_ACCESS;
例如读第0个bar里偏移为4的寄存器,
UINT8 Data;
Status = PciIo->Io.Read(PciIo,
EfiPciIoWidthUint8 , //读一个字节
0, // Bar 0
4, // 偏移4字节
1, // 一个元素
&Data);
AC97
Intel芯片组南桥中一般有AC97控制器芯片,AC97控制器通过AC-LINK与AC97 Codec进行通信,我们的音频驱动其实是AC97控制器驱动。
如何操作AC97控制器可以参考Intel® I/O Controller Hub 6 (ICH6) High Definition Audio / AC ’97, 这儿只做简单介绍。
AC97 IO空间有两个部分:Native Audio Bus Master Control Registers and Native Mixer Registers。
Bar0 表示 Native Mixer Registers(NAMBAR) , 访问以16-bits为一个单位(与AC-LINK单位数据大小一致)。 用于配置AC97参数,如音量,采样率等。
Bar1 表示Bus Master Control Registers 。 用于控制AC97
Native Mixer Registers 功能表
Primary Offset NAMBAR Exposed Registers
(Codec ID =00)
00h Reset
02h Master Volume
04h Aux Out Volume
06h Mono Volume
08h Master Tone (R & L)
0Ah PC_BEEP Volume
0Ch Phone Volume
0Eh Mic Volume
10h Line In Volume
12h CD Volume
14h Video Volume
16h Aux In Volume
18h PCM Out Volume
1Ah Record Select
1Ch Record Gain
1Eh Record Gain Mic
20h General Purpose
22h 3D Control
24h AC ’97 RESERVED
26h Powerdown Ctrl/Stat
28h Extended Audio
2Ah Extended Audio Ctrl/Stat
2Ch PCM Front DAC Rate
2Eh PCM Surround DAC Rate
30h PCM LFE DAC Rate
32h PCM LR ADC Rate
34h MIC ADC Rate
36h 6Ch Vol: C, LFE
38h 6Ch Vol: L, R Surround
3Ah S/PDIF Control
3C~56h Intel RESERVED
58h AC ’97 Reserved
5Ah Vendor Reserved
7Ch Vendor ID1
7Eh Vendor ID2
Bus Master Control Registers 功能表
Offset
|
Mnemonic
|
Name
|
Default
|
Access
|
00h
|
PI_BDBAR
|
PCM In Buffer Descriptor list Base Address
|
00000000h
|
R/W
|
04h
|
PI_CIV
|
PCM In Current Index Value
|
00h
|
RO
|
05h
|
PI_LVI
|
PCM In Last Valid Index
|
00h
|
R/W
|
06h
|
PI_SR
|
PCM In Status
|
0001h
|
R/WC, RO
|
08h
|
PI_PICB
|
PCM In Position in Current Buffer
|
0000h
|
RO
|
0Ah
|
PI_PIV
|
PCM In Prefetched Index Value
|
00h
|
RO
|
0Bh
|
PI_CR
|
PCM In Control
|
00h
|
R/W, R/W (special)
|
10h
|
PO_BDBAR
|
PCM Out Buffer Descriptor list Base Address
|
00000000h
|
R/W
|
14h
|
PO_CIV
|
PCM Out Current Index Value
|
00h
|
RO
|
15h
|
PO_LVI
|
PCM Out Last Valid Index
|
00h
|
R/W
|
16h
|
PO_SR
|
PCM Out Status
|
0001h
|
R/WC, RO
|
18h
|
PO_PICB
|
PCM In Position In Current Buffer
|
0000h
|
RO
|
1Ah
|
PO_PIV
|
PCM Out Prefetched Index Value
|
00h
|
RO
|
1Bh
|
PO_CR
|
PCM Out Control
|
00h
|
R/W, R/W (special)
|
20h
|
MC_BDBAR
|
Mic. In Buffer Descriptor List Base Address
|
00000000h
|
R/W
|
24h
|
MC_CIV
|
Mic. In Current Index Value
|
00h
|
RO
|
25h
|
MC_LVI
|
Mic. In Last Valid Index
|
00h
|
R/W
|
26h
|
MC_SR
|
Mic. In Status
|
0001h
|
R/WC, RO
|
28h
|
MC_PICB
|
Mic. In Position In Current Buffer
|
0000h
|
RO
|
2Ah
|
MC_PIV
|
Mic. In Prefetched Index Value
|
00h
|
RO
|
2Bh
|
MC_CR
|
Mic. In Control
|
00h
|
R/W, R/W (special)
|
2Ch
|
GLOB_CNT
|
Global Control
|
00000000h
|
R/W, R/W (special)
|
30h
|
GLOB_STA
|
Global Status
|
See register description
|
R/W, R/WC, RO
|
34h
|
CAS
|
Codec Access Semaphore
|
00h
|
R/W (special)
|
40h
|
MC2_BDBAR
|
Mic. 2 Buffer Descriptor List Base Address
|
00000000h
|
R/W
|
44h
|
MC2_CIV
|
Mic. 2 Current Index Value
|
00h
|
RO
|
45h
|
MC2_LVI
|
Mic. 2 Last Valid Index
|
00h
|
R/W
|
46h
|
MC2_SR
|
Mic. 2 Status
|
0001h
|
RO, R/WC
|
48h
|
MC2_PICB
|
Mic 2 Position In Current Buffer
|
0000h
|
RO
|
4Ah
|
MC2_PIV
|
Mic. 2 Prefetched Index Value
|
00h
|
RO
|
4Bh
|
MC2_CR
|
Mic. 2 Control
|
00h
|
R/W, R/W (special)
|
50h
|
PI2_BDBAR
|
PCM In 2 Buffer Descriptor List Base
Address
|
00000000h
|
R/W
|
54h
|
PI2_CIV
|
PCM In 2 Current Index Value
|
00h
|
RO
|
55h
|
PI2_LVI
|
PCM In 2 Last Valid Index
|
00h
|
R/W
|
56h
|
PI2_SR
|
PCM In 2 Status
|
0001h
|
R/WC, RO
|
58h
|
PI2_PICB
|
PCM In 2 Position in Current Buffer
|
0000h
|
RO
|
5Ah
|
PI2_PIV
|
PCM In 2 Prefetched Index Value
|
00h
|
RO
|
5Bh
|
PI2_CR
|
PCM In 2 Control
|
00h
|
R/W, R/W (special)
|
60h
|
SPBAR
|
S/PDIF Buffer Descriptor List Base Address
|
00000000h
|
R/W
|
64h
|
SPCIV
|
S/PDIF Current Index Value
|
00h
|
RO
|
65h
|
SPLVI
|
S/PDIF Last Valid Index
|
00h
|
R/W
|
66h
|
SPSR
|
S/PDIF Status
|
0001h
|
R/WC, RO
|
68h
|
SPPICB
|
S/PDIF Position In Current Buffer
|
0000h
|
RO
|
6Ah
|
SPPIV
|
S/PDIF Prefetched Index Value
|
00h
|
RO
|
6Bh
|
SPCR
|
S/PDIF Control
|
00h
|
R/W, R/W (special)
|
80h
|
SDM
|
SData_IN Map
|
00h
|
R/W, RO
|
表中有5个通道
PI = PCM in channel
PO = PCM out channel
MC = Mic in channel
MC2 = Mic 2 channel
PI2 = PCM in 2 channel
SP = S/PDIF out channel.
每个通道有一个16-bit DMA引擎,用于传输音频数据(PCM格式)。 DMA引擎使用Buffer Descriptor List 数据结构获得数据缓冲区地址。Buffer Descriptor List 最多允许32项,Buffer Descriptor 声明如下:
typedef struct {
UINT32 addr;
UINT16 len;
unsigned short reserved:14;
unsigned short BUP:1;
unsigned short IOC:1;
} BufferDescriptor;
addr的第0个bit必须是0。 List中最后一个
BufferDescriptor 的BUP需为1。每个buffer最多有65536个采样。
启动DMA的过程如下(翻译自:Intel® I/O Controller Hub 6 (ICH6) High Definition Audio / AC ’97 Programmer’s Reference Manual (PRM) :30页):
1. 建立buffer descriptor list。
2. 将buffer descriptor list 的基地址写入Buffer Descriptor List Base Address register(对PCM Out而言是 MBBAR + 10h (POBAR))
3. 填充buffer descriptor list 中的buffer descriptor,并设置好数据缓存。
4. 设置 Last Valid Index(LVI)寄存器 (PCM OUT: MBBAR + 15h (POLVI))。LVI是BDL中最后一个准备好缓冲区的buffer descriptor的下标。
5. 设置Control register中的run bit,启动DMA传输。
这时就可以听到声音了。
AC97驱动现在我们开始设计AC97驱动。时候还记得我们讲过驱动分两个部分,硬件相关部分和框架部分。首先来设计硬件相关部分,也就是驱动硬件并提供给用户使用的协议。
EFI_AUDIO_PROTOCOL
我们把要提供的服务命名为EFI_AUDIO_PROTOCOL,在EFI_AUDIO_PROTOCOL, 我们提供播放音频的服务,首先我们要提供硬件初始化服务,还要提供PCM音频播放服务,音量调节服务,还要提供一个Event用来通知用户音频播放结束。
在audio.h中定义EFI_AUDIO_PROTOCOL相关数据结构
struct _EFI_AUDIO_PROTOCOL{
UINT64 Revision;
EFI_AC97_RESET Reset;
EFI_AC97_PLAY Play;
EFI_AC97_VOLUME Volume;
EFI_EVENT WaitForEndEvent;
}
设计EFI_AUDIO_PROTOCOL的GUID
#define EFI_AUDIO_PROTOCOL_GUID \
{ \
0xce345171, 0xabcd, 0x11d2, {0x8e, 0x4f, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b } \
}
在accdriver.c中还要定义用于标示音频播放上下文的数据结构,一般命名为X_PRIVATE_DATA。在上下文中我们要包含EFI_AUDIO_PROTOCOL实例,设备的EFI_PCI_IO_PROTOCOL实例,BufferDescriptor。
typedef struct {
UINTN Signature;
EFI_AUDIO_PROTOCOL Audio;
EFI_PCI_IO_PROTOCOL *PciIo;
BufferDescriptor Bdes[32];
} AUDIO_PRIVATE_DATA;
定义EFI_AUDIO_PROTOCOL Template,用于在Start函数中初始化EFI_AUDIO_PROTOCOL实例
//
// Template for Audio private data structure.
// The pointer to Audio protocol interface is assigned dynamically.
//
AUDIO_PRIVATE_DATA gDiskIoPrivateDataTemplate = {
AUDIO_PRIVATE_DATA_SIGNATURE,
{
EFI_AUDIO_PROTOCOL_REVISION,
AC97Reset,
AC97Play,
AC97Volume,
0
},
NULL,
{0}
};
下面要实现 EFI_AUDIO_PROTOCOL 中定义的三个服务,每个成员函数(服务)的第一个参数是This指针,在函数里首先要根据This指针获得上下文Private,然后根据上下文执行相应操作。
首先是Reset,主要是设置Mixer Register里德Powerdown Ctrl/Stat和Reset寄存器, 可以参考WinDDK里AC97驱动示例。
/**
@param This Indicates a pointer to the calling context.
@retval EFI_SUCCESS .
@retval EFI_DEVICE_ERROR .
**/
EFI_STATUS
EFIAPI AC97Reset(
IN EFI_AUDIO_PROTOCOL *This
)
{
NTSTATUS InitAC97 (void);
EFI_STATUS Status;
AUDIO_PRIVATE_DATA* Private = AUDIO_PRIVATE_DATA_FROM_THIS(This);
gAudioPciIo = Private->PciIo;
Status = InitAC97 ();
return Status;
}
然后是Play函数, 首先设置Buffer Descriptor List,然后设置POBAR和
PO_LVI , 最后启动DMA。/**
@param This Indicates a pointer to the calling context.
@param PcmData Pointer to PCM Data
@param Format PCM Data Format
@param Size How many Samples in PCM Data
@retval EFI_SUCCESS .
@retval EFI_DEVICE_ERROR .
**/
EFI_STATUS
EFIAPI AC97Play(
IN EFI_AUDIO_PROTOCOL *This,
IN UINT8* PcmData,
IN UINT32 Format,
IN UINTN Size
)
{
EFI_STATUS Status;
UINTN i=0, LenLeft = Size;
AUDIO_PRIVATE_DATA* Private = AUDIO_PRIVATE_DATA_FROM_THIS(This);
gAudioPciIo = Private->PciIo;
for( i=0; i< 32 && LenLeft > 0; i++, LenLeft-=65536){
Private->Bdes[0].addr = (u32)(PcmData + 65536 * 4 * i);
Private->Bdes[0].len = (u16)(LenLeft <= 65536 ? LenLeft:LenLeft-65536);
}
Private->Bdes[i-1].BUP = 1;
Private->Bdes[i-1].IOC = 0;
WriteBMControlRegister32(PO_BDBAR , (u32)Private->Bdes);
WriteBMControlRegister(PO_LVI , 0);
WriteBMControlRegisterMask(PO_CR , 1,1); //启动DMA
(void) Status;
return EFI_SUCCESS;
} 设置音量的函数
/**
@param This Indicates a pointer to the calling context.
@param Increase How much should the volume change,
+Number increase; -Number Decrease.
@param NewVolume if *NewVolume >=0 , It will set the volume as *NewVolume;
if *NewVolume <0, the Volume will be changed by Increase,
and *Newvolume returns the current Volume.
@retval EFI_SUCCESS .
@retval EFI_DEVICE_ERROR .
**/
EFI_STATUS
EFIAPI AC97Volume(
IN EFI_AUDIO_PROTOCOL *This,
IN INT32 Increase,
IN OUT INT32 * NewVolume
)
{
AUDIO_PRIVATE_DATA* Private = AUDIO_PRIVATE_DATA_FROM_THIS(This);
gAudioPciIo = Private->PciIo;
if(*NewVolume < 0){
WORD data= (WORD) (long ) NewVolume;
WriteCodecRegister (AC97REG_PCM_OUT_VOLUME, data, 0xFFFF);
}else{
WORD data= 0;
ReadCodecRegister(AC97REG_PCM_OUT_VOLUME, &data);
data += (INT16) Increase;
WriteCodecRegister (AC97REG_PCM_OUT_VOLUME, data, 0xFFFF);
*NewVolume = (INT32)data;
}
return EFI_SUCCESS;
}
驱动的框架部分驱动的框架部分主要是实现EFI_DRIVER_BINDING_PROTOCOL及Image的初始化函数
//
// Driver binding protocol implementation for AC97 driver.
//
EFI_DRIVER_BINDING_PROTOCOL gAudioDriverBinding = {
AC97DriverBindingSupported,
AC97DriverBindingStart,
AC97DriverBindingStop,
0xa,
NULL,
NULL
};
(1)Supported(
AC97DriverBindingSupported)函数用来检测设备是否AC97驱动器。分两步:1。 判断Controller时候有
EFI_PCI_IO_PROTOCOL , 没有则返回错误。2。 有EFI_PCI_IO_PROTOCOL 则读取PCI配置空间,判断设备时候AC97驱动器。
/**
Test to see if this driver supports ControllerHandle.
@param This Protocol instance pointer.
@param ControllerHandle Handle of device to test
@param RemainingDevicePath Optional parameter use to pick a specific child
device to start.
@retval EFI_SUCCESS This driver supports this device
@retval EFI_ALREADY_STARTED This driver is already running on this device
@retval other This driver does not support this device
**/
EFI_STATUS
EFIAPI
AC97DriverBindingSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
EFI_STATUS Status;
PCI_TYPE00 PciData;
EFI_PCI_IO_PROTOCOL *PciIo;
Status = gBS->OpenProtocol(
ControllerHandle,
&gEfiPciIoProtocolGuid,
(VOID**)&PciIo,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
return Status;
}
Status = PciIo->Pci.Read (
PciIo,
EfiPciIoWidthUint32,
0,
sizeof (PciData) / sizeof (UINT32),
&PciData
);
gBS->CloseProtocol (
ControllerHandle,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
if (EFI_ERROR (Status)) {
return Status;
}
if (!(PciData.Hdr.ClassCode[2] == PCI_CLASS_MEDIA && PciData.Hdr.ClassCode[1] == PCI_CLASS_MEDIA_AUDIO && PciData.Hdr.ClassCode[0] == 0x00) ) {
return EFI_UNSUPPORTED;
}
return EFI_SUCCESS;
}
(2) Start(
AC97DriverBindingStart) 启动设备,安装EFI_AUDIO_PROTOCOL。
/**
Start this driver on ControllerHandle by opening a PCI IO protocol and
installing a Audio IO protocol on ControllerHandle.
@param This Protocol instance pointer.
@param ControllerHandle Handle of device to bind driver to
@param RemainingDevicePath Optional parameter use to pick a specific child
device to start.
@retval EFI_SUCCESS This driver is added to ControllerHandle
@retval EFI_ALREADY_STARTED This driver is already running on ControllerHandle
@retval other This driver does not support this device
**/
EFI_STATUS
EFIAPI
AC97DriverBindingStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
EFI_STATUS Status;
EFI_PCI_IO_PROTOCOL *PciIo;
AUDIO_PRIVATE_DATA *Private;
Status = gBS->OpenProtocol(
ControllerHandle,
&gEfiPciIoProtocolGuid,
(VOID**)&PciIo,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Allocate a buffer to store the ATA_ATAPI_PASS_THRU_INSTANCE data structure
//
Private = AllocateCopyPool (sizeof (AUDIO_PRIVATE_DATA), &gDiskIoPrivateDataTemplate );
if (Private == NULL) {
goto ErrorExit;
}
Private->PciIo = PciIo;
Status = gBS->CreateEvent(EVT_NOTIFY_WAIT, TPL_NOTIFY, (EFI_EVENT_NOTIFY)PlayEndEventNoify, (VOID*)Private, &Private->Audio.WaitForEndEvent);
Status = gBS->InstallProtocolInterface (
&ControllerHandle,
&gEfiAudioProtocolGUID,
EFI_NATIVE_INTERFACE,
&Private->Audio
);
ErrorExit:
if (EFI_ERROR (Status)) {
if (Private != NULL) {
FreePool (Private);
}
gBS->CloseProtocol (
ControllerHandle,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
}else{
// Init Ac97
AC97Reset(&Private->Audio);
}
return Status;
}
(3)Stop(
AC97DriverBindingStop)函数
/**
Stop this driver on ControllerHandle by removing Audio IO protocol and closing
the PCI IO protocol on ControllerHandle.
@param This Protocol instance pointer.
@param ControllerHandle Handle of device to stop driver on
@param NumberOfChildren Number of Handles in ChildHandleBuffer. If number of
children is zero stop the entire bus driver.
@param ChildHandleBuffer List of Child Handles to Stop.
@retval EFI_SUCCESS This driver is removed ControllerHandle
@retval other This driver was not removed from this device
**/
EFI_STATUS
EFIAPI
AC97DriverBindingStop (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN UINTN NumberOfChildren,
IN EFI_HANDLE *ChildHandleBuffer
)
{
EFI_STATUS Status;
AUDIO_PRIVATE_DATA *Private;
EFI_AUDIO_PROTOCOL *Audio;
//
// Get our context back.
//
Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiAudioProtocolGUID,
(VOID **) &Audio,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR (Status)) {
return EFI_UNSUPPORTED;
}
Private = AUDIO_PRIVATE_DATA_FROM_THIS(This);
Status = gBS->UninstallProtocolInterface (
ControllerHandle,
&gEfiAudioProtocolGUID,
&Private->Audio
);
if (!EFI_ERROR (Status)) {
Status = gBS->CloseProtocol (
ControllerHandle,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
}
if (!EFI_ERROR (Status)) {
FreePool (Private);
}
return Status;
}
最后要在Image的入口函数安装EFI_DRIVER_BINDING_PROTOCOL
EFI_STATUS
EFIAPI
InitializeACC(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
//
// Install driver model protocol(s).
//
Status = EfiLibInstallDriverBindingComponentName2 (
ImageHandle,
SystemTable,
&gAudioDriverBinding,
ImageHandle,
&gAudioComponentName,
&gAudioComponentName2
);
//ASSERT_EFI_ERROR (Status);
return Status;
}
自此,驱动模型就介绍完了。