string

string
posts - 27, comments - 177, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

UEFI实战(5) driver

Posted on 2012-04-22 08:36 djx_zh 阅读(16283) 评论(10)  编辑 收藏 引用
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 \
{ \
    
0xce3451710xabcd0x11d2, {0x8e0x4f0x00xa00xc90x690x720x3b } \
}

在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;
}
自此,驱动模型就介绍完了。 

Feedback

# re: UEFI实战(5) driver  回复  更多评论   

2012-04-23 08:49 by tb
学习了

# re: UEFI实战(5) driver  回复  更多评论   

2012-04-23 11:41 by ningle
你好!请教:EfiLibInstallDriverBindingComponentName2()中实参, &gAudioComponentName, &gAudioComponentName2是在什么时候初始化的,哪个文件中被初始化的,被谁调用后初始化的?

# re: UEFI实战(5) driver[未登录]  回复  更多评论   

2012-04-24 01:41 by djx_zh
@ningle
EFI_COMPONENT_NAME_PROTOCOL 和EFI_COMPONENT_NAME2_PROTOCOL 一般放在CompontName.c中
GLOBAL_REMOVE_IF_UNREFERENCED EFI_COMPONENT_NAME_PROTOCOL gAudioComponentName = {
AudioComponentNameGetDriverName,
AudioComponentNameGetControllerName,
"eng"
};

GLOBAL_REMOVE_IF_UNREFERENCED EFI_COMPONENT_NAME2_PROTOCOL gAudioComponentName2 = {
(EFI_COMPONENT_NAME2_GET_DRIVER_NAME) AudioComponentNameGetDriverName,
(EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME) AudioComponentNameGetControllerName,
"en"
};

GLOBAL_REMOVE_IF_UNREFERENCED EFI_UNICODE_STRING_TABLE mAudioDriverNameTable[] = {
{
"eng;en",
(CHAR16 *)L"Generic AC97 Driver"
},
{
NULL,
NULL
}
};

EFI_STATUS
EFIAPI
AudioComponentNameGetDriverName (
IN EFI_COMPONENT_NAME_PROTOCOL *This,
IN CHAR8 *Language,
OUT CHAR16 **DriverName
)
{
return LookupUnicodeString2 (
Language,
This->SupportedLanguages,
mAudioDriverNameTable,
DriverName,
(BOOLEAN)(This == &gAudioComponentName)
);
}


EFI_STATUS
EFIAPI
AudioComponentNameGetControllerName (
IN EFI_COMPONENT_NAME_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_HANDLE ChildHandle OPTIONAL,
IN CHAR8 *Language,
OUT CHAR16 **ControllerName
)
{
return EFI_UNSUPPORTED;
}

# re: UEFI实战(5) driver  回复  更多评论   

2013-12-19 11:07 by lingming
学习了,期望有更多的好文章,或者编本书。

# re: UEFI实战(5) driver[未登录]  回复  更多评论   

2013-12-23 22:13 by djx_zh
多谢关注。 如果一切顺利的话,新书将于4月份发行。到时将有ffdecoder, ffplayer, CppPkg, GuiPkg 等源码随书发行。

# re: UEFI实战(5) driver  回复  更多评论   

2014-05-16 11:00 by 何龙
请问UEFI驱动怎么实现在PCI扫描完之后所有的挂载的设备再加载你上面的驱动?

# re: UEFI实战(5) driver[未登录]  回复  更多评论   

2014-05-27 01:51 by djx_zh
把UEFI驱动(必须遵循UEFI驱动规范)烧到rom里,系统会自动加载这个驱动

# re: UEFI实战(5) driver  回复  更多评论   

2014-07-14 15:36 by 赵岗
你好,能不能发我一份源码,谢谢
我的邮箱是867368106@qq.com

# re: UEFI实战(5) driver  回复  更多评论   

2014-07-17 16:58 by P_WU
你好,也能不能发我一份源码,急需,谢谢
Email: stylewarp@hotmail.com

# re: UEFI实战(5) driver  回复  更多评论   

2014-09-26 09:08 by djx_zh
勘误:
设置 Last Valid Index(LVI)寄存器 (PCM OUT: MBBAR + 15h (POLVI))。LVI是BDL中最后一个准备好缓冲区的buffer descriptor的下标。

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理