PCI9054驱动设计开发

     首先是选用开发工具。设备驱动这东东没有工具好像是没有办法做的。一年前我把自己智力估计过高,一头扎进了DDK里面,结果是按照书上做东东,做出来也不明白是什么意思。然后就搁浅了。后来用WinDriver,也没有什么概念性的提高。
      现在不做不行了。可选的还有DS,这个与VC结合很好的东东,虽然别人说有这样那样的问题,但是还是有很多人在用。我想就从这里开始吧。
      2003-3-18日,安装编译了HelloDrv,当然这个东东肯定能通过的,就是设置两个环境变量的问题。一个是$BASEDIR,设到DDK的路径,我的路径是G:/2kDDK,然后就是$CPU选项,废话,当然是i386.这两个变量可以在Numega里设置,只能用一次,也可以在系统里面设置我开始设置在Numega里,后来移到系统设置里了。
     然后安装HelloDrv,测试HelloDrv,当然成功了,这个东东是最简单的东东。
     2003-3-19日,上午去听了一上午无聊的SCADA,下午开始研究昨天晚上下来的资料。WDM驱动程序有一个主要的初始化入口点,即一个必须称为DriverEntry的例程。它有一个标准的函数原型。当WDM驱动程序被装入时,内核调用DriverEntry例程。看到这句话,我第一个反应就是到Numega生成的代码中区寻找这个东东。代码是C++封装的。大概在这个地方

这里做的事情好像就是从注册表里面读取了一个参数。我理解读写等等IRP处理函数都应该在这里注册,怎么没有看到呢?是不是全部被Numega
封装了呢?我不得其解。也许我到了一定境界,可以看懂DS的源代码才能明白?
      然后有点茫然不知所措。再研究Numega生成的代码,发现主要有三部分组成。
      一部分是class MyWDM0 : public KDriver,主要有AddDevice,DriverEntry,和LoadRegistryParamete三个函数还有一个变量ULONG m_bBreakOnEntry;是接收注册表中参数的。我不知道我在哪一步生成了它。
      第二部分是class MyWDM0Device : public KPnpDevice看上去应该是设备驱动的主体。因为我看到了读写IRP的处理,设备控制IRP的处理等等。打住,我们先看看到底有多少种IRP需要处理:
1 驱动程序的初始化。
2 创建和删除设备。
3 处理Win32程序打开和关闭文件句柄的请求。
4 处理Win32程序输入/输出请求。
5 实现对设备的串行化访问。
6 访问硬件。
7 取消I/O请求。
8 超时I/O请求。
9 调用其他驱动程序。
10 处理电源管理请求。
11 使用Windows管理诊断(WMI)向系统管理员报告。
12 处理一个可热插/拔的设备被加入或删除的情况。
      第三部分是class MyWDM0Device_DriverManagedQueue : public KDriverManagedQueueEx,应该是一个管理性质的东东。第二部分的对象实例化的过程就在这里面的StartIo函数里面运行。

     好了,终于找到IRP处理的入口了。这里只处理了四个IRP,其余的都被传走了。我们看看到底程序从这里开始都做了些什么?
     首先是MyWDM0Device的构造函数。

     这里做的是一些初始化的工作,初始化了一个KPnpLowerDevice m_Lower;这个不知道是个什么设备。看到这,这里又看到了一个奇怪的东东,MyWDM0Device_DriverManagedQueue m_DriverManagedQueue;奇怪了。明明Device类是在这里实例化的,怎么又成了Device的成员变量呢?KPnpDevice的构造又做了什么事情呢?
     要搞明白这个,看样子还是要去看Numega的源码。55555555
     我们去看KPnpDevice的构造去。否则程序看到这就没有办法再看下去了。
     2003-3-20 最终也没有从KPnpDevice的源代码中看出什么名堂,我回想起当初看MFC源代码的时侯了,当时也是怎么也看不懂,不过现在
就好多了,看样子这不是一天的功夫就可以搞明白的了.
      看不下程序就不看了,随便做做就好.今天想完成应用层到驱动程序的读写.最初的思想是很简单的:
      在驱动中申请一块内存,然后在整个驱动运行期间保持这块内存,在写的时侯把应用内存复制到驱动内存中,读的时侯把驱动内存复制到应用内存中.
      然后就开始写了我自己的第一行驱动代码:

      PUCHAR m_pDataBuffer;
      这个是作为我的驱动内存指针,放在MyWDM0Device类里面然后在MyWDM0Device的构造里面申请内存.好像没有人说不能用malloc,我就用它吧。
       m_pDataBuffer = malloc(1024);
       然后就出错了,告诉我找不到malloc的说明。当然我可以不管这些,去找出malloc的原型,但是我不敢这麽做。我想肯定是Numega
为了防止我这种菜鸟乱用这些危险的函数屏蔽掉了。也许这是危险的。
       ChrisCant说:在DispatchLevel使用分页内存会崩溃,在PassiveLevel使用分页内存会阻塞,非分页内存不可以申请太大,资源宝贵
然后就迷惑了,怎样申请一段内存呢?
        m_pDataBuffer = (PUCHAR)ExAllocatePool(NonPagedPool,1024);
        //分配1024字节作为数据寄存区。所以读写不可超过1024字节。
       这一句是从ChrisCant的源代码中得到的。我不管了,先看看编译的效果。

       编译通过了。但是我还不敢就这麽安装上去。申请了内存不释放是不可以的,我在析构里释放内存。
       if( m_pDataBuffer!=NULL) ExFreePool(m_pDataBuffer);
       当然这一句也是抄来的。我不清楚析构在什么时侯会被调用,是不是真的就能够释放这一片内存。自我安慰吧,怎么办呢?
      然后就是急功近利的读写了。我用了另外一个可能是危险的函数memcpy .内存的问题总是非常复杂,不知道会发生什么样的结果,Chris
说内存需要用内存自旋锁,我实在不知道这是个什么东东,先不用它。
     memcpy((void *)m_pDataBuffer,(void *)pBuffer,dwTotalSize);
     dwBytesSent = dwTotalSize;
     I.Information() = dwBytesSent;
     I.Status() = status;
    这是写IRP处理程序中加的,读处理也是类似。然后编译,测试。
    居然通过了。其中的pBuffer指针是Numega给我的框架中从IRP得到的,dwTotalSize也是.当然我还是不能就这麽使用memcpy 的。我想应该也是用DDK中那些莫名其妙的函数比较合适。再看Chris的源代码。
    RtlCopyMemory( Irp->AssociatedIrp.SystemBuffer, Buffer+FilePointer, BytesTxd);
我抄,我抄,我抄抄抄!
出错了,安装正确,测试结果没有出现我想要的东东呢。
再查出错的地方,可能是指针弄错了。调整了一下,好了没有问题了。
再加点防御性代码。
我的第一个设备驱动就这麽做好了。这个驱动能完成什么功能?嘿嘿,天晓得,大概可以做一个1024字节大小的共享内存映象文件吧。微软早就提供这种东东了。不过这是我自己的哦。
然后就不知道该做什么了。我的目标是PLX9054,然后就开始研究9054这东东。
开发9054最好的东东是什么?废话,当然是PLX公司提供的SDK了。
     20:13 2003-4-4停顿了好多天,开发图形界面去了.终于开发到了一定阶段.正好搞来一块PLX9054RDK板.
     这东东搞到手还真费力气,今天开始研究继续我的菜鸟开发之路.
     先是胆战心惊地将RDK插到插槽中.这是一块好像很旧的板子,要是烧了我的HP工作站可就让我心痛到S了.好在没有问题.开机安装软件.我早就把SDK安装好了,剩下的事情应该是即插即用工作了.
     PLX9054 SDK是这麽一套东东,它先做了一个低层驱动,然后....也就是说,驱动程序并不要自己去做了,只要用用就好.但该怎么用呢????
    我对此现在还是一无所知.不过我很庆幸,我的条件可能是一般人很难得到的: 想开发设备驱动,就能得到Numega,想对付9054 ,就有了RDK和SDK.
悲惨世界了。干了半天,什么都没有保存下来,就死机了。一般来说,玩硬件和驱动想不死机真是圣人和傻子才能做到。我不想再弄一台机器来了,再来我就被屏幕包围了。
     然后继续我的历程:
     首先是看看设备被安装成了什么样子了:
Window报告:
PCI 9054 RDK-LITE Board,位置: PCI Slot3,总线2 设备11,功能0
资源设置:
内存范围: FFDDFF00 - FFDDFFFF 256
I/O范围:  FE00 - FEFF 256
内存范围: FD000000 - FDFFFFFF 16M
内存范围: FFDE0000 - FFDFFFFF 256K
中断请求: 18
然后看PLXMON的信息;胡乱做了一下测试,又死机了。真XXXXXX
先看PCR 这东东称做PCI 寄存器。
00 字节是设备号和销售商。 540610B5 54代表为PLX9054 ,06 表示桥。10B5大概是PLX公司的意思。
04 字节是命令状态寄存器。 02900117 里面的选项有:支持新能力,支持快速背对背传输操作,设备选择断言时间1错误状态无,命令方式有:I/O,内存空间操作,总线主能力,内存写/失效能力,系统错误能力,
08 字节是类代码和版本。0680000B,06表示为桥设备,80代表其他桥类型。00是规定的编程接口,0B是版本号。
0C 字节是Cache行容量寄存器,00002008,MINDSHARE公司的书上对它的说明我没有看懂。
10 字节是基地址,FFDDFF00,前面FFDDFF是基地址,后面的00 表示不可预取,可以定位在4GB以下的任意空间,内存。
14 字节是基地址,0000FE01,前面的30位是基地址,最后两位表示是I/O空间。
18 字节是基地址,FD000000,同10字节
1C 字节是基地址,FFDE0000,同10字节
20 和24 字节也是基地址,但在RDK-LITE中不可用,值为00000000
28 字节是CardBus CIS指针,指向CardBus卡的卡信息结构(CIS),CardBus是另外一种体系结构,能和PCI共享芯片。
我们永远都不能明白所有的东西。
2C 字节是子系统设备和销售商ID,905410B5,好理解吧。
30 字节是扩展ROM基地址,值为00000000,扩展ROM译码被禁止。
34 字节表示下一个功能点。值为00000040 ,表示下一个扩展功能寄存器在00000040 位置上。
38 字节保留,值为00000000
3C 字节为中断管脚和中断线,00000112,01表示对应CPU的第一个中断引脚(INTA#)12表示使用18(10进制)号中断。
40 字节开始是扩展功能点。这个功能是电源管理。我们暂时不管电源的问题。00014800,48表示下一个功能点。
44 字节也是电源管理,
48 字节是热交换能力,00004C06,4C是下一个功能点,06表示能力号,具体什么意思我没有研究。
4C 字节是VPD,VPD的意思是关键产品数据(VitalProductData),00000003 ,0000 表示VPD地址,00是下一个功能点,没有下一个功能。03 是功能号,VPD的功能号就是这个。
50 字节是VPD的数据,000000000,基本上我们没有使用VPD。尽管VPD成为关键,但它从
来就不是关键的。
PCR就这麽多,下面看看LCR。这东东称做本地配置寄存器。
LCR 我在书上没有找到论述,可能是PLX公司自己的东东。
首先是Space 和扩展ROM。
Space0 范围: FF000000,对应PCR中的18 字节,这样理解应该10字节的基地址是寄存器使用的。
Space0 Remap:00000000,没有使能。这表明Space0不可用,两次死机估计都是我操作这个空间引起的。
我不知道RDK-LITE有没有配置这16M的内存,板子插进去了,我又不想拿出来看。
扩展ROM范围:00000000,没有扩展ROM
扩展ROM Remap: 00000000,没有当然无法使用。
然后是Space0和扩展ROM的描述字。8B430043,内存空间0-32位,使能TA/Ready输入,使能突发传送,不允许无限制突发传送,等待时间为0,可能是无限。扩展ROM空间32位,使能TA/Ready输入。我们没办法使用它。
还有读取扩展长数据从EEPROM,De-assert TRDY when Wite FIFO之类的,我也不懂。

然后是Space1
Space1 范围 FFFE0000,128KB,这个空间是可用的,我用它读写成功过。
然后是Space Remap,20000001,不知道为什么是20000001。20000000应该是512M的内存之外,01是干什么的???
Space1 描述: 00000143 ,01估计是空间号,43 和Space0 描述的一样。
然后是DirectMaster。不知道DirectMaster是什么意思。先不看也罢。
其他的有VPD,这东东我不关心,Mode/DMAArbitraction,0101000C,不明白什么意思。

后面是RTR,这东东是运行期间寄存器,里面定义了8个邮槽(Mailbox),我对邮槽没有概念,除了第4个邮槽为013B4000,第5个为00010000外,其余都是0;
然后是Doorbells 控制。PCI到Local的门铃和Local到PCI的门铃都配置了00000000,中断允许了PCI中断和Local中断,其他都是禁止的。
也就是说,Doorbell好像没有投入使用。
后面是EEPROM和用户IO控制,180F677E,选项太多,不想看了。
然后是硬编码设备ID和版本号,905410B5,0000000C,后面怎么会是12 的我也没有想明白。也许是指EEPROM中软件版本号?
后面是DMA控制,DMA通道都被禁止了,模式为00000043,和内存映射一致。虽然我最终是想用DMA的,但现在先不看也罢。
最后是MCR,为信息序列寄存器。
看不懂。看样子要先研究以下9054 的资料才能明白。
然后看到,LCR是在9054的EEPROM里面的。应该上面这些东东都在这里面的。
然后看看内存。 DMA内存缓冲区有0x17000字节长度。应该是65536+7*256*16 = 94208字节大小。是可读写的。没有问题。
显示的内存为00FF20000到00FF36ffff
S1 显示的内存为01FC0000 到01FDffff,试图填充1FFFF以后的偏移数据,引起了操作系统内存错误。
也就是说:仅有128K内存可用,系统将它们映射到了内存地址空间。

    11:02 2003-4-7没有什么好看的了。
    我们看看PLXMON的说明书对上面的理解有什么说法。不要急于编程,编程有的是机会呢。
    直接看9054寄存器设置。好像里面没有什么有新意的地方,连新名词都没有。只是一些界面说明。废话。
    再看看9054RDKLITE的资料,两页纸,打印出来就是了。
    Based on the powerful PLX PCI 9054, the PLX PCI 9054RDK-LITE (RDK-LITE) provides a low cost development environment for PCI 9054 embedded application designs.
At the heart of the RDK-LITE is the PLX PCI 9054 I/O Accelerator which supports a 32-bit, 33MHz PCI bus and a 50MHz local bus. It provides a complete PCI v2.2 specification implementation enabling burst transfers up to 132 Mbytes/second. The PCI 9054 incorporates PLX’s industry leading advanced Data Pipe ArchitectureTM technology including dual DMA engines, programmable PCI Initiator and Target data transfer modes and PCI messaging functions.
    There are various sizes of QFP footprints and through hole matrixes on the PCI board which support industry standard embedded CPUs, DSPs and PLCC packaging, including the IBM PPC, Motorola MPC and Coldfire, Hitachi MCU and MPU, TI DS
P, Analog Device DSP and many others. The 100-pin PLX Option Module (POM) J-Bus connector can be used to connect to any POM. Also there is a 30 x 25 0.1” through hole grid spacing for expansion purposes.
   The RDK-LITE comes with PLXMon99TM, a comprehensive GUI utility for monitoring, debugging, configuration and code download. When used with the PLX PCI SDK, which can be purchased separately from PLX, the PCI 9054RDK-LITE will allow customers to quickly and easily add PCI to a variety of embedded applications in telecommunications, data communications, and many others.

   A PCI prototype adapter board design based on the PLX PCI 9054 32-bit I/O Accelerator chip n Surface mount footprints on
the board support industry standard embedded CPUs, DSPs and PLCC packaging including the IBM PPC, Motorola MPC and Coldfire, Hitachi MCU and MPU, TI DSP, Analog Device DSP and many others n 30 x 25 0.1” through hole grid space for expansion purpose n PLXMon 99, a comprehensive GUI utility for debugging, configuration and code download
    首先可以看出的是,PCI9054RDK-LITE是一个基于32位IO加速芯片PLXPCI9054的原型版。
    PCI9054 支持32为,33MPCI总线和50M局部总线。提供PCI2.2版的能力,突发传输速度132M,然后是广告,支持最多64MSDRAM,板载128KSRAM,上面的16M内存一直不可用,不知道什么原因。既然如此,我们先使用这128K的静态RAM吧。
   再看9054 的广告。产品说明书700多页,对出学者很困难,广告总是容易一点。广告中只能提供一个简单的概念性的东东。大致的数据通道是可以想像的。9054一方面挂接在局部总线上,另一方面挂接在PCI总线上,然后为局部内存到计算机内存提供传输加速。
   再看看9054的说明资料,这是个700多页的大东东。实在不想看。直接看看9054的SDK说明书。首先是软件体系构架。最上层当然是应用程序。SDK提供了PLXMON的源代码,可以参照实现。然后是API的动态链接库。然后是设备驱动,PCI总线9054本地API,BSP,BEM,这些东东大多只提供到PowerPC的芯片用。可能不需要软件根它们打交道。
本地应用程序。
设备驱动和DLL都是SDK提供了的,当然设备驱动有源代码,DLL有没有我不清楚。
我们直接看应用层的一个例子。
又死机了。写了好多东东都丢了。看了一下9054本地寄存器的配置
Register 11-39. (LAS0BA; PCI:04h, LOC:84h) Local Address Space 0 Local Base
Address (Remap)
Bit Description Read Write
Value after
Reset
0
Space 0 Enable. Writing a 1 enables decoding of PCI addresses for PCI
Target access to Local Bus Space 0. Writing a 0 disables decoding.
Yes Yes 0
1 Reserved. Yes No 0
3:2
If Local Bus Space 0 is mapped into Memory space, bits are not used. When
mapped into I/O space, included with bits [31:4] for remapping.
Yes Yes 00
31:4
Remap PCI Address to Local Address Space 0 into Local Address Space.
Bits in this register remap (replace) PCI Address bits used in decode as Loc
al
Address bits.
Note: Remap Address value must be a multiple of the Range (not the Range
register).
Yes Yes 0h
上面一堆乱七八糟的东东是从PDF文件的表格里拷贝下来的。中心思想是Remap这个寄存
器怎样设置
Space 0 Enable. Writing a 1 enables decoding of PCI addresses for PCI
Target access to Local Bus Space 0. Writing a 0 disables decoding.
是说空间0 的允许。填1表示使能PCI地址译码操作局部总线的0空间。乱七八糟,我都不
知道在说什么,总之,试验的结果,
填1就好了。
然后我想可以写程序了吧。
首先当然是吧PLXAPI.h 包含到工程里。这没有任何问题。
其次我想应该是去寻找PLXAPI.lib或者干脆把这个库弄进来。
简单起见,我把它弄到工程里算了。
然后可以开始编程了。
步骤1:初始化PLX卡。
S8
SelectDevice(
DEVICE_LOCATION *pDevice
)
{
U32 i;
U32 DeviceNum;
pDevice->BusNumber = (U8)-1;
pDevice->SlotNumber = (U8)-1;
pDevice->VendorId = (U16)-1;
pDevice->DeviceId = (U16)-1;
strcpy(pDevice->SerialNumber, "");
DeviceNum = FIND_AMOUNT_MATCHED;
if (PlxPciDeviceFind(
pDevice,
&DeviceNum
) != ApiSuccess)
{
return 0;
}
if (DeviceNum == 0)
return 0;
PlxPrintf("/n");
for (i=0; i<DeviceNum; i++)
{
pDevice->BusNumber = (U8)-1;
pDevice->SlotNumber = (U8)-1;
pDevice->VendorId = (U16)-1;
pDevice->DeviceId = (U16)-1;
strcpy(pDevice->SerialNumber, "");
PlxPciDeviceFind(
pDevice,
&i
);
PlxPrintf(
"/t/t %u. %.4x %.4x [%04x - bus %.2x slot %.2x]/n",
i+1, pDevice->DeviceId, pDevice->VendorId,
DetermineChipType(pDevice), pDevice->BusNumber, pDevice->SlotNumber
);
}
PlxPrintf("/t/t 0. Cancel/n/n");
PlxPrintf("/t Device Selection --> ");
while ((i = Plx_getch()-'0') > DeviceNum);
PlxPrintf("%u/n/n", i);
if (i == 0)
return -1;
pDevice->BusNumber = (U8)-1;
pDevice->SlotNumber = (U8)-1;
pDevice->VendorId = (U16)-1;
pDevice->DeviceId = (U16)-1;
strcpy(pDevice->SerialNumber, "");
i--;
PlxPciDeviceFind(
pDevice,
&i
);
return (S8)DeviceNum;
}
这里核心的函数是PlxPciDeviceFind,
我们从SDK的文档中去查找这个函数:
PLX SDK Programmer’s Reference Manual v3.2
3-90 ? 2001 PLX Technology, Inc. All rights reserved.
PlxPciDeviceFind
Syntax:
RETURN_CODE
PlxPciDeviceFind(
DEVICE_LOCATION *device,
U32 *requestLimit
);
看这个原型,返回值的类型为S8,这个类型在Win32系统中被定义为signed char ,是一
个有符号的字符。
参数为一个类型为DEVICE_LOCATION 的指针,和一个返回设备数量的指针。U32的类型当
然应该是32位无符号数。
PLX Chip Support:All
Description:Finds PLX devices on the PCI bus given a combination of bus numb
er, slot number; vendor ID, and/or
device ID, or by the Serial number.
Parameters:
device:A pointer to a DEVICE_LOCATION structure containing the search crite
ria and/or to contain device
information once it is found.
requestLimit:A pointer to a 32-bit buffer. Refer to the Notes section.
Return Codes:
ApiSuccess :The function returned successfully
ApiNullParam :One or more parameters is NULL
ApiInvalidDeviceInfo :The device information did not match any device in th
e system
ApiNoActiveDriver: There is no device driver installed into the system
Notes:
If requestLimit contains the value FIND_AMOUNT_MATCHED, the DEVICE_LOCATION
structure should
contain the search criteria
If requestLimit contains a value other than FIND_AMOUNT_MATCHED, the DEVICE_
LOCATION
structure will be filled in with information about the device indexed at req
uestLimit (numbering starts at 0).
Set items not to be used as search criteria to –1.
If the SerialNumber element within the DEVICE_LOCATION structure is not bein
g used as a search
criterion the first character should be set to an empty string; otherwise, t
he Serial number takes
precedence over all other search criteria.
Usage:
U32 ReqLimit;
RETURN_CODE rc;
DEVICE_LOCATION Device;
// Query to get the total number of PLX devices
ReqLimit = FIND_AMOUNT_MATCHED;
// No search criteria, select all devices
Device.BusNumber = (U32)-1;
Device.SlotNumber = (U32)-1;
Device.VendorId = (U32)-1;
Device.DeviceId = (U32)-1;
Device.SerialNumber[0] = ’/0’;
rc = PlxPciDeviceFind(
&Device,
&ReqLimit
);
if ((rc != ApiSuccess) || (ReqLimit == 0))
{
// ERROR – Unable to locate any valid devices
}
ReqLimit = FIND_AMOUNT_MATCHED;
// Search for the first device matching a specific Vendor ID
Device.BusNumber = (U32)-1;
Device.SlotNumber = (U32)-1;
Device.VendorId = 0x10b5; // PLX Vendor ID
Device.DeviceId = (U32)-1;
Device.SerialNumber[0] = ’/0’;
rc = PlxPciDeviceFind(
&Device,
&ReqLimit
);
if (rc != ApiSuccess)
{
// ERROR – Unable to locate search for any matching devices
}
if (ReqLimit == 0)
{
// ERROR – Unable to locate any matching devices
}
else
{
// Found a device – fill in the device information
PlxPciDeviceFind(
&Device,
&ReqLimit
);
}
上面给出的是查找设备的一个例子。
剩下的事情应该是打开设备了。
HANDLE hDevice; //这个句柄是以后交互用的句柄,应该是一个全局变量。现在不知道
一个
//进程能不能同时打开两次这个设备,也不知道多个进程能不能同时打开这个设备。
rc = PlxPciDeviceOpen(&Device,&hDevice);
if (rc != ApiSuccess)
{
MessageBox("无法打开设备,通讯不能进行","警告",MB_OK);
}
然后,合格的程序员首先要考虑的是关闭设备,释放资源。
PlxPciDeviceClose( hDevice);

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值