source: trunk/src/kernel32/hmdevio.cpp@ 7457

Last change on this file since 7457 was 7457, checked in by sandervl, 24 years ago

preliminary work on overlapped serial comm IO

File size: 20.4 KB
Line 
1/* $Id: hmdevio.cpp,v 1.16 2001-11-26 14:54:00 sandervl Exp $ */
2
3/*
4 * Win32 Device IOCTL API functions for OS/2
5 *
6 * Copyright 1998 Sander van Leeuwen
7 *
8 *
9 * Project Odin Software License can be found in LICENSE.TXT
10 *
11 */
12#define INCL_DOSPROFILE
13#define INCL_DOSDEVICES
14#define INCL_DOSDEVIOCTL
15#define INCL_GPI
16#define INCL_DOSFILEMGR /* File Manager values */
17#define INCL_DOSERRORS /* DOS Error values */
18#define INCL_DOSPROCESS /* DOS Process values */
19#define INCL_DOSMISC /* DOS Miscellanous values */
20#include <os2wrap.h> //Odin32 OS/2 api wrappers
21#include <string.h>
22#include <stdio.h>
23
24#include <win32type.h>
25#include <win32api.h>
26#include <misc.h>
27#include <win\winioctl.h>
28#include "hmdevio.h"
29#include "cio.h"
30#include "map.h"
31#include "exceptutil.h"
32
33#define DBG_LOCALLOG DBG_hmdevio
34#include "dbglocal.h"
35
36static BOOL fX86Init = FALSE;
37//SvL: Used in iccio.asm (how can you put these in the .asm data segment without messing things up?)
38ULONG ioentry = 0;
39USHORT gdt = 0;
40char devname[] = "/dev/fastio$";
41
42static BOOL GpdDevIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped);
43static BOOL MAPMEMIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped);
44static BOOL FXMEMMAPIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped);
45static BOOL VPCIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped);
46
47static WIN32DRV knownDriver[] =
48 {{"\\\\.\\GpdDev", "", TRUE, 666, GpdDevIOCtl},
49 { "\\\\.\\MAPMEM", "PMAP$", FALSE, 0, MAPMEMIOCtl},
50 { "FXMEMMAP.VXD", "PMAP$", FALSE, 0, FXMEMMAPIOCtl},
51#if 1
52 { "\\\\.\\VPCAppSv", "", TRUE, 667, VPCIOCtl}};
53#else
54 };
55#endif
56
57static int nrKnownDrivers = sizeof(knownDriver)/sizeof(WIN32DRV);
58BOOL fVirtualPC = FALSE;
59
60//******************************************************************************
61//******************************************************************************
62void RegisterDevices()
63{
64 HMDeviceDriver *driver;
65 DWORD rc;
66
67 for(int i=0;i<nrKnownDrivers;i++)
68 {
69 driver = new HMDeviceDriver(knownDriver[i].szWin32Name,
70 knownDriver[i].szOS2Name,
71 knownDriver[i].fCreateFile,
72 knownDriver[i].devIOCtl);
73
74 rc = HMDeviceRegister(knownDriver[i].szWin32Name, driver);
75 if (rc != NO_ERROR) /* check for errors */
76 dprintf(("KERNEL32:RegisterDevices: registering %s failed with %u.\n",
77 knownDriver[i].szWin32Name, rc));
78 }
79
80 //check registry for Odin driver plugin dlls
81 HKEY hkDrivers, hkDrvDll;
82
83 rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
84 "System\\CurrentControlSet\\Services",
85 0, KEY_READ, &hkDrivers);
86
87 if(rc == 0) {
88 char szDllName[CCHMAXPATH];
89 char szKeyName[CCHMAXPATH];
90 char szDrvName[CCHMAXPATH];
91 DWORD dwType, dwSize;
92 int iSubKey = 0;
93
94 while(rc == 0) {
95 rc = RegEnumKeyA(hkDrivers, iSubKey++, szKeyName, sizeof(szKeyName));
96 if(rc) break;
97
98 rc = RegOpenKeyExA(hkDrivers, szKeyName,
99 0, KEY_READ, &hkDrvDll);
100 if(rc == 0) {
101 dwSize = sizeof(szDllName);
102 rc = RegQueryValueExA(hkDrvDll,
103 "DllName",
104 NULL,
105 &dwType,
106 (LPBYTE)szDllName,
107 &dwSize);
108
109 RegCloseKey(hkDrvDll);
110 if(rc == 0 && dwType == REG_SZ)
111 {
112 HINSTANCE hDrvDll = LoadLibraryA(szDllName);
113 if(hDrvDll) {
114 sprintf(szDrvName, "\\\\.\\%s", szKeyName);
115 driver = new HMCustomDriver(hDrvDll, szDrvName);
116
117 rc = HMDeviceRegister(szDrvName, driver);
118 if (rc != NO_ERROR) /* check for errors */
119 dprintf(("KERNEL32:RegisterDevices: registering %s failed with %u.\n", szDrvName, rc));
120 }
121 }
122 rc = 0;
123 }
124 }
125 RegCloseKey(hkDrivers);
126 }
127
128 return;
129}
130//******************************************************************************
131//******************************************************************************
132BOOL WIN32API RegisterCustomDriver(PFNDRVOPEN pfnDriverOpen, PFNDRVCLOSE pfnDriverClose,
133 PFNDRVIOCTL pfnDriverIOCtl, LPCSTR lpDeviceName)
134{
135 HMDeviceDriver *driver;
136 DWORD rc;
137
138 driver = new HMCustomDriver(pfnDriverOpen, pfnDriverClose, pfnDriverIOCtl, lpDeviceName);
139 if(driver == NULL) {
140 DebugInt3();
141 return FALSE;
142 }
143 rc = HMDeviceRegister((LPSTR)lpDeviceName, driver);
144 if (rc != NO_ERROR) { /* check for errors */
145 dprintf(("KERNEL32:RegisterDevices: registering %s failed with %u.\n", lpDeviceName, rc));
146 return FALSE;
147 }
148 return TRUE;
149}
150//******************************************************************************
151//******************************************************************************
152HMDeviceDriver::HMDeviceDriver(LPCSTR lpDeviceName, LPSTR lpOS2DevName, BOOL fCreate,
153 WINIOCTL pDevIOCtl)
154 : HMDeviceKernelObjectClass(lpDeviceName)
155{
156 this->fCreateFile = fCreateFile;
157 this->szOS2Name = lpOS2DevName;
158 this->devIOCtl = pDevIOCtl;
159}
160//******************************************************************************
161//******************************************************************************
162HMDeviceDriver::HMDeviceDriver(LPCSTR lpDeviceName)
163 : HMDeviceKernelObjectClass(lpDeviceName)
164{
165}
166//******************************************************************************
167//******************************************************************************
168DWORD HMDeviceDriver::CreateFile (HANDLE hHandle,
169 LPCSTR lpFileName,
170 PHMHANDLEDATA pHMHandleData,
171 PVOID lpSecurityAttributes,
172 PHMHANDLEDATA pHMHandleDataTemplate)
173{
174 APIRET rc;
175 HFILE hfFileHandle = 0L; /* Handle for file being manipulated */
176 ULONG ulAction = 0; /* Action taken by DosOpen */
177 ULONG sharetype = 0;
178
179 if(pHMHandleData->dwAccess & (GENERIC_READ | GENERIC_WRITE))
180 sharetype |= OPEN_ACCESS_READWRITE;
181 else
182 if(pHMHandleData->dwAccess & GENERIC_WRITE)
183 sharetype |= OPEN_ACCESS_WRITEONLY;
184
185 if(pHMHandleData->dwShare == 0)
186 sharetype |= OPEN_SHARE_DENYREADWRITE;
187 else
188 if(pHMHandleData->dwShare & (FILE_SHARE_READ | FILE_SHARE_WRITE))
189 sharetype |= OPEN_SHARE_DENYNONE;
190 else
191 if(pHMHandleData->dwShare & FILE_SHARE_WRITE)
192 sharetype |= OPEN_SHARE_DENYREAD;
193 else
194 if(pHMHandleData->dwShare & FILE_SHARE_READ)
195 sharetype |= OPEN_SHARE_DENYWRITE;
196
197 if(szOS2Name[0] == 0) {
198 pHMHandleData->hHMHandle = 0;
199 return (NO_ERROR);
200 }
201
202tryopen:
203 rc = DosOpen( szOS2Name, /* File path name */
204 &hfFileHandle, /* File handle */
205 &ulAction, /* Action taken */
206 0,
207 FILE_NORMAL,
208 FILE_OPEN,
209 sharetype,
210 0L); /* No extended attribute */
211
212 if(rc == ERROR_TOO_MANY_OPEN_FILES) {
213 ULONG CurMaxFH;
214 LONG ReqCount = 32;
215
216 rc = DosSetRelMaxFH(&ReqCount, &CurMaxFH);
217 if(rc) {
218 dprintf(("DosSetRelMaxFH returned %d", rc));
219 return rc;
220 }
221 dprintf(("DosOpen failed -> increased nr open files to %d", CurMaxFH));
222 goto tryopen;
223 }
224
225 dprintf(("DosOpen %s returned %d\n", szOS2Name, rc));
226
227 if(rc == NO_ERROR) {
228 pHMHandleData->hHMHandle = hfFileHandle;
229 return (NO_ERROR);
230 }
231 else return(rc);
232}
233//******************************************************************************
234//******************************************************************************
235BOOL HMDeviceDriver::CloseHandle(PHMHANDLEDATA pHMHandleData)
236{
237 DWORD rc = 0;
238
239 if(pHMHandleData->hHMHandle) {
240 rc = DosClose(pHMHandleData->hHMHandle);
241 }
242 pHMHandleData->hHMHandle = 0;
243 return rc;
244}
245//******************************************************************************
246//******************************************************************************
247BOOL HMDeviceDriver::DeviceIoControl(PHMHANDLEDATA pHMHandleData, DWORD dwIoControlCode,
248 LPVOID lpInBuffer, DWORD nInBufferSize,
249 LPVOID lpOutBuffer, DWORD nOutBufferSize,
250 LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
251{
252 return devIOCtl(pHMHandleData->hHMHandle, dwIoControlCode, lpInBuffer, nInBufferSize,
253 lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped);
254}
255//******************************************************************************
256//******************************************************************************
257static BOOL GpdDevIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
258{
259 ULONG port, val = 0;
260
261 if(fX86Init == FALSE) {
262 if(io_init() == 0)
263 fX86Init = TRUE;
264 else return(FALSE);
265 }
266
267 *lpBytesReturned = 0;
268
269 port = ((GENPORT_WRITE_INPUT *)lpInBuffer)->PortNumber;
270
271 switch((dwIoControlCode >> 2) & 0xFFF) {
272 case IOCTL_GPD_READ_PORT_UCHAR:
273 if(nOutBufferSize < sizeof(char))
274 return(FALSE);
275
276 val = c_inb(port);
277 *(char *)lpOutBuffer = val;
278 *lpBytesReturned = sizeof(char);
279 break;
280 case IOCTL_GPD_READ_PORT_USHORT:
281 if(nOutBufferSize < sizeof(USHORT))
282 return(FALSE);
283
284 val = c_inw(port);
285 *(USHORT *)lpOutBuffer = val;
286 *lpBytesReturned = sizeof(USHORT);
287 break;
288 case IOCTL_GPD_READ_PORT_ULONG:
289 if(nOutBufferSize < sizeof(ULONG))
290 return(FALSE);
291
292 val = c_inl(port);
293 *(ULONG *)lpOutBuffer = val;
294 *lpBytesReturned = sizeof(ULONG);
295 break;
296 case IOCTL_GPD_WRITE_PORT_UCHAR:
297 val = ((GENPORT_WRITE_INPUT *)lpInBuffer)->CharData;
298 c_outb(port, val);
299 break;
300 case IOCTL_GPD_WRITE_PORT_USHORT:
301 val = ((GENPORT_WRITE_INPUT *)lpInBuffer)->ShortData;
302 c_outw(port, val);
303 break;
304 case IOCTL_GPD_WRITE_PORT_ULONG:
305 val = ((GENPORT_WRITE_INPUT *)lpInBuffer)->LongData;
306 c_outl(port, val);
307 break;
308 default:
309 dprintf(("GpdDevIOCtl unknown func %X\n", (dwIoControlCode >> 2) & 0xFFF));
310 return(FALSE);
311 }
312
313 return(TRUE);
314}
315//******************************************************************************
316//******************************************************************************
317static BOOL MAPMEMIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
318{
319 PHYSICAL_MEMORY_INFO *meminfo = (PHYSICAL_MEMORY_INFO *)lpInBuffer;
320 struct map_ioctl memmap;
321
322 *lpBytesReturned = 0;
323
324 switch((dwIoControlCode >> 2) & 0xFFF) {
325 case IOCTL_MAPMEM_MAP_USER_PHYSICAL_MEMORY:
326 if(nInBufferSize != sizeof(PHYSICAL_MEMORY_INFO))
327 return(FALSE);
328
329 memmap.a.phys = meminfo->BusAddress.u.LowPart;
330 memmap.size = meminfo->Length;
331 dprintf(("DeviceIoControl map phys address %X length %X\n", memmap.a.phys, memmap.size));
332 if(mpioctl((HFILE)hDevice, IOCTL_MAP, &memmap) == -1) {
333 dprintf(("mpioctl failed!\n"));
334 return(FALSE);
335 }
336
337 dprintf(("DeviceIoControl map virt address = %X\n", memmap.a.user));
338 *(ULONG *)lpOutBuffer = (ULONG)memmap.a.user;
339 break;
340 case IOCTL_MAPMEM_UNMAP_USER_PHYSICAL_MEMORY:
341 dprintf(("Unmap mapping %X\n", *(ULONG *)lpInBuffer));
342 memmap.a.phys = *(ULONG *)lpInBuffer;
343 memmap.size = 0;
344 if(mpioctl((HFILE)hDevice, IOCTL_MAP, &memmap) == -1)
345 return(FALSE);
346 break;
347 default:
348 dprintf(("MAPMEMIOCtl unknown func %X\n", (dwIoControlCode >> 2) & 0xFFF));
349 return(FALSE);
350 }
351
352 return(TRUE);
353}
354//******************************************************************************
355//******************************************************************************
356static BOOL FXMEMMAPIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
357{
358 struct map_ioctl memmap;
359 MAPDEVREQUEST *vxdmem = (MAPDEVREQUEST *)lpInBuffer;
360
361 switch(dwIoControlCode) {
362 case 1:
363 break;
364 case 2:
365 memmap.a.phys = (DWORD)vxdmem->mdr_PhysicalAddress;
366 memmap.size = vxdmem->mdr_SizeInBytes;
367 dprintf(("DeviceIoControl map phys address %X length %X\n", memmap.a.phys, memmap.size));
368 if(mpioctl((HFILE)hDevice, IOCTL_MAP, &memmap) == -1) {
369 dprintf(("mpioctl failed!\n"));
370 return(FALSE);
371 }
372
373 dprintf(("DeviceIoControl map virt address = %X\n", memmap.a.user));
374 vxdmem->mdr_LinearAddress = (PVOID)memmap.a.user;
375 break;
376 default:
377 dprintf(("FXMEMMAPIOCtl unknown func %X\n", (dwIoControlCode >> 2) & 0xFFF));
378 return(FALSE);
379 }
380
381 return(TRUE);
382}
383//******************************************************************************
384//******************************************************************************
385static BOOL VPCIOCtl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
386{
387 APIRET rc;
388
389 dprintf(("VPCIOCtl func %x: %x %d %x %d %x %x", dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped));
390 switch(dwIoControlCode) {
391 case 0x9C402880: //0x00
392 if(nOutBufferSize < 4) {
393 SetLastError(ERROR_BAD_LENGTH);
394 return FALSE;
395 }
396 *(DWORD *)lpOutBuffer = 0x60001;
397 *lpBytesReturned = 4;
398 return TRUE;
399
400 case 0x9C402894: //0x14 (get IDT table)
401 {
402 DWORD *lpBuffer = (DWORD *)lpOutBuffer;
403 if(nOutBufferSize < 0x800) {
404 SetLastError(ERROR_BAD_LENGTH);
405 return FALSE;
406 }
407 memset(lpOutBuffer, 0, nOutBufferSize);
408 for(int i=0;i<32;i++) {
409 lpBuffer[i*2] = 0x0178c4c8;
410 lpBuffer[i*2+1] = 0xfff18F00;
411 }
412 for(i=0x50;i<0x57;i++) {
413 lpBuffer[i*2] = 0x0178c4c8;
414 lpBuffer[i*2+1] = 0xfff18E00;
415 }
416 for(i=0x70;i<0x77;i++) {
417 lpBuffer[i*2] = 0x0178c4c8;
418 lpBuffer[i*2+1] = 0xfff18E00;
419 }
420 lpBuffer[0x4F*2] = 0x0178c4c8;
421 lpBuffer[0x4F*2+1] = 0xfff18E00;
422 lpBuffer[0xEF*2] = 0x0178c4c8;
423 lpBuffer[0xEF*2+1] = 0xfff18E00;
424 *lpBytesReturned = 0xFF*8;
425 return TRUE;
426 }
427 case 0x9C40288C: //0x0C change IDT
428 if(nInBufferSize < 0x22) {
429 SetLastError(ERROR_BAD_LENGTH);
430 return FALSE;
431 }
432 fVirtualPC = TRUE;
433 return TRUE;
434
435 case 0x9C402884: //0x04 ExAllocatePoolWithTag
436 {
437 DWORD *lpBuffer = (DWORD *)lpInBuffer;
438 if(nInBufferSize < 0x08) {
439 SetLastError(ERROR_BAD_LENGTH);
440 return FALSE;
441 }
442 dprintf(("In: %x %x", lpBuffer[0], lpBuffer[1]));
443 return TRUE;
444 }
445
446 case 0x9C402898: //0x18 Remove IDT patch
447 if(nInBufferSize < 0x01) {
448 SetLastError(ERROR_BAD_LENGTH);
449 return FALSE;
450 }
451 fVirtualPC = FALSE;
452 return TRUE;
453 default:
454 dprintf(("VPCIOCtl unknown func %X\n", dwIoControlCode));
455 return FALSE;
456 }
457}
458//******************************************************************************
459//******************************************************************************
460HMCustomDriver::HMCustomDriver(HINSTANCE hInstance, LPCSTR lpDeviceName)
461 : HMDeviceDriver(lpDeviceName), hDrvDll(0)
462{
463 hDrvDll = hInstance ;
464 *(ULONG *)&driverOpen = (ULONG)GetProcAddress(hDrvDll, "DrvOpen");
465 *(ULONG *)&driverClose = (ULONG)GetProcAddress(hDrvDll, "DrvClose");
466 *(ULONG *)&driverIOCtl = (ULONG)GetProcAddress(hDrvDll, "DrvIOCtl");
467}
468//******************************************************************************
469//******************************************************************************
470HMCustomDriver::HMCustomDriver(PFNDRVOPEN pfnDriverOpen, PFNDRVCLOSE pfnDriverClose,
471 PFNDRVIOCTL pfnDriverIOCtl, LPCSTR lpDeviceName)
472 : HMDeviceDriver(lpDeviceName), hDrvDll(0)
473{
474 driverOpen = pfnDriverOpen;
475 driverClose = pfnDriverClose;
476 driverIOCtl = pfnDriverIOCtl;
477}
478//******************************************************************************
479//******************************************************************************
480HMCustomDriver::~HMCustomDriver()
481{
482 if(hDrvDll) FreeLibrary(hDrvDll);
483}
484//******************************************************************************
485//******************************************************************************
486DWORD HMCustomDriver::CreateFile (LPCSTR lpFileName,
487 PHMHANDLEDATA pHMHandleData,
488 PVOID lpSecurityAttributes,
489 PHMHANDLEDATA pHMHandleDataTemplate)
490{
491 pHMHandleData->hHMHandle = driverOpen(pHMHandleData->dwAccess, pHMHandleData->dwShare);
492 if(pHMHandleData->hHMHandle == 0) {
493 return 2;
494 }
495 return 0;
496}
497//******************************************************************************
498//******************************************************************************
499BOOL HMCustomDriver::CloseHandle(PHMHANDLEDATA pHMHandleData)
500{
501 if(pHMHandleData->hHMHandle) {
502 driverClose(pHMHandleData->hHMHandle);
503 }
504 pHMHandleData->hHMHandle = 0;
505 return TRUE;
506}
507//******************************************************************************
508//******************************************************************************
509BOOL HMCustomDriver::DeviceIoControl(PHMHANDLEDATA pHMHandleData, DWORD dwIoControlCode,
510 LPVOID lpInBuffer, DWORD nInBufferSize,
511 LPVOID lpOutBuffer, DWORD nOutBufferSize,
512 LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
513{
514 BOOL ret;
515
516 ret = driverIOCtl(pHMHandleData->hHMHandle, dwIoControlCode, lpInBuffer, nInBufferSize,
517 lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped);
518 dprintf(("DeviceIoControl %x returned %d", dwIoControlCode, ret));
519 return ret;
520}
521//******************************************************************************
522//******************************************************************************
523BOOL WIN32API QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount)
524{
525 QWORD time;
526 APIRET rc;
527
528 rc = DosTmrQueryTime(&time);
529 if(rc) {
530 dprintf(("DosTmrQueryTime returned %d\n", rc));
531 return(FALSE);
532 }
533 lpPerformanceCount->u.LowPart = time.ulLo;
534 lpPerformanceCount->u.HighPart = time.ulHi;
535 return(TRUE);
536}
537//******************************************************************************
538//******************************************************************************
539BOOL WIN32API QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency)
540{
541 APIRET rc;
542 ULONG freq;
543
544 rc = DosTmrQueryFreq(&freq);
545 if(rc) {
546 dprintf(("DosTmrQueryFreq returned %d\n", rc));
547 return(FALSE);
548 }
549 lpFrequency->u.LowPart = freq;
550 lpFrequency->u.HighPart = 0;
551 dprintf2(("QueryPerformanceFrequency returned 0x%X%X\n", lpFrequency->u.HighPart, lpFrequency->u.LowPart));
552 return(TRUE);
553}
554//******************************************************************************
555//******************************************************************************
Note: See TracBrowser for help on using the repository browser.