//**************************************************************************** // // Module: Unimdm // File: devioctl.c // // Copyright (c) 1992-1995, Microsoft Corporation, all rights reserved // // Revision History // // // 5/16/95 Viroon Touranachun Moved from mdmutil.c // // // Description: Interface to kernel-mode unimodem // //**************************************************************************** #include "unimdm.h" #include "umdmspi.h" #ifdef WINNT #ifndef USE_SERVICECONTROLLER // # error "Unimplemented" #define MODEM_SERVICE_NAME \ L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\modem" #endif // USE_SERVICECONTROLLER // Global variable for the modem.sys service BOOL bModemSysStarted = FALSE; CRITICAL_SECTION ServiceControlerCriticalSection; #endif // WINNT LONG WINAPI MCXSetModemSettings( HANDLE hModem, PMODEMSETTINGS lpMS ); //**************************************************************************** // DWORD MapMcxResult (DWORD) // // Function: Maps internal error to a standard error code. // // Returns: standard error code // // Fri 14-Apr-1995 12:47:26 -by- Viroon Touranachun [viroont] // created //**************************************************************************** DWORD MapMcxResult (DWORD dwResult) { switch (dwResult) { case MDM_SUCCESS: return ERROR_SUCCESS; case MDM_PENDING: return ERROR_IO_PENDING; default: return ERROR_IO_DEVICE; } } #ifdef USE_SERVICECONTROLLER LONG WINAPI StartModemDriver( VOID ) { LONG lResult=ERROR_SUCCESS; // Assume success BOOL bResult; SC_HANDLE schModemSys; SC_HANDLE schSCManager; SERVICE_STATUS ServiceStatus; EnterCriticalSection( &ServiceControlerCriticalSection ); if (!bModemSysStarted) { schSCManager=OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS ); if (schSCManager != NULL) { // // now on service // schModemSys=OpenService( schSCManager, TEXT("modem"), SERVICE_START | SERVICE_STOP | SERVICE_QUERY_STATUS ); if (schModemSys == NULL) { DPRINTF("OpenService() for modem.sys failed!"); CloseServiceHandle(schSCManager); lResult=ERROR_IO_DEVICE; goto Leave; } bResult=QueryServiceStatus( schModemSys, &ServiceStatus ); if (bResult) { if (ServiceStatus.dwCurrentState != SERVICE_RUNNING) { bResult=StartService( schModemSys, 0, NULL ); if (bResult) { DPRINTF("StartService() for modem.sys succeeded!"); bModemSysStarted=TRUE; } else { DPRINTF("StartService() for modem.sys FAILED!"); lResult = GetLastError(); } } else { bModemSysStarted=TRUE; } } else { lResult = GetLastError(); DPRINTF1("QueryServiceStatus() for modem.sys failed (%d)!", lResult); } CloseServiceHandle(schModemSys); CloseServiceHandle(schSCManager); } else { DPRINTF("OpenSCManager() failed!"); lResult=ERROR_IO_DEVICE; } // if opened SC macanger } else { // // already running // } Leave: LeaveCriticalSection( &ServiceControlerCriticalSection ); return lResult; } LONG WINAPI StopModemDriver( VOID ) { LONG lResult; BOOL bResult; SC_HANDLE schModemSys; SC_HANDLE schSCManager; SERVICE_STATUS ServiceStatus; EnterCriticalSection( &ServiceControlerCriticalSection ); if (bModemSysStarted) { schSCManager=OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS ); if (schSCManager != NULL) { // // now on service // schModemSys=OpenService( schSCManager, TEXT("modem"), SERVICE_START | SERVICE_STOP | SERVICE_QUERY_STATUS ); if (schModemSys != NULL) { bResult=ControlService( schModemSys, SERVICE_CONTROL_STOP, &ServiceStatus ); if (bResult) { bModemSysStarted=TRUE; } CloseServiceHandle(schModemSys); } CloseServiceHandle(schSCManager); } } LeaveCriticalSection( &ServiceControlerCriticalSection ); return ERROR_SUCCESS; } #else // !USE_SERVICECONTROLLER BOOL WINAPI ObtainLoadDriverPrivilege( IN PBOOLEAN WasEnabled ) { NTSTATUS Status; DPRINTF("ObtainLoadDriverPrivilege"); // // Obtain the process's access token for the current thread // Status = RtlImpersonateSelf(SecurityImpersonation); if (!NT_SUCCESS(Status)) { DPRINTF1("RtlImpersonateSelf returned 0x%08x", Status); return FALSE; } // // request "Load-Driver" privileges for this thread // Status = RtlAdjustPrivilege( SE_LOAD_DRIVER_PRIVILEGE, TRUE, TRUE, WasEnabled ); if (!NT_SUCCESS(Status)) { DPRINTF1("RtlAdjustPrivileges returned 0x%08x", Status); RevertToSelf(); return FALSE; } return TRUE; } VOID WINAPI ReleaseLoadDriverPrivilege( IN PBOOLEAN WasEnabled ) { NTSTATUS Status; DPRINTF("ReleaseLoadDriverPrivilege"); // // See if we had to enable SE_LOAD_DRIVER_PRIVILEGE // if (!*WasEnabled) { // // relinquish "Load-Driver" privileges for this thread // Status = RtlAdjustPrivilege( SE_LOAD_DRIVER_PRIVILEGE, FALSE, TRUE, WasEnabled ); } // // return the thread to its previous access token // RevertToSelf(); } // # error "Unimplemented" static WCHAR g_rgwchBuffer[] = MODEM_SERVICE_NAME; static UNICODE_STRING g_usDriverName = { sizeof(g_rgwchBuffer)-sizeof(WCHAR), //Length sizeof(g_rgwchBuffer), //MaximumLength g_rgwchBuffer //Buffer }; LONG WINAPI StartModemDriver( VOID ) { LONG lResult=ERROR_SUCCESS; EnterCriticalSection( &ServiceControlerCriticalSection ); while (!bModemSysStarted) { // breakout-construct NTSTATUS nts; BOOLEAN WasEnabled; // // Enable our load-driver privilege // if (!ObtainLoadDriverPrivilege(&WasEnabled)) { lResult = ERROR_ACCESS_DENIED; break; } // // Load modem.sys // nts = NtLoadDriver(&g_usDriverName); if (NT_SUCCESS(nts) || nts==STATUS_IMAGE_ALREADY_LOADED) { DPRINTF1("NtLoadDriver returns %s", (nts==STATUS_IMAGE_ALREADY_LOADED) ? TEXT("Already loaded") : TEXT("Success")); bModemSysStarted=TRUE; } else { DPRINTF1("ERROR: NtLoadDriver fails(0x%lx)", (DWORD) nts); lResult=ERROR_IO_DEVICE; } // // relinquish "Load-Driver" privileges for this thread // ReleaseLoadDriverPrivilege(&WasEnabled); break; } LeaveCriticalSection( &ServiceControlerCriticalSection ); return lResult; } LONG WINAPI StopModemDriver( VOID ) { LONG lResult=ERROR_SUCCESS; EnterCriticalSection( &ServiceControlerCriticalSection ); while (bModemSysStarted) { // break-out construct NTSTATUS nts; BOOLEAN WasEnabled; // // Enable our load-driver privilege // if (!ObtainLoadDriverPrivilege(&WasEnabled)) { lResult = ERROR_ACCESS_DENIED; break; } // // Unload modem.sys // nts = NtUnloadDriver(&g_usDriverName); if (NT_SUCCESS(nts)) { DPRINTF("NtUnloadDriver succeeded"); bModemSysStarted=FALSE; } else { DPRINTF1("ERROR: NtUnloadDriver fails(0x%lx)", (DWORD) nts); lResult=ERROR_IO_DEVICE; } // // relinquish "Load-Driver" privileges for this thread // ReleaseLoadDriverPrivilege(&WasEnabled); break; } LeaveCriticalSection( &ServiceControlerCriticalSection ); return lResult; } #endif // !USE_SERVICECONTROLLER //**************************************************************************** // DWORD OpenModem (PLINEDEV) // // Function: Opens the modem device. // // Notes: This function never returns success synchrnously // // Returns: ERROR_IO_PENDING if the operation will complete asynchronously // an error code for synchrnous failure // // Fri 14-Apr-1995 12:47:26 -by- Viroon Touranachun [viroont] // created //**************************************************************************** DWORD OpenModem(PLINEDEV pLineDev, LPBYTE lpComConfig, DWORD dwSize) { HANDLE hComm; DCB dcb; TCHAR szPort[MAXDEVICENAME+1]; DWORD dwRet; SERVICE_STATUS ServiceStatus; BOOL bResult; TSPPRINTF("Open modem"); dwRet=StartModemDriver(); if (dwRet != ERROR_SUCCESS) { return dwRet; } // Initialize szPort to be "\\.\" lstrcpy(szPort, cszDevicePrefix); // Concatenate FriendlyName onto szPort to form "\\.\Modem Name" lstrcat(szPort, pLineDev->szDeviceName); TSPPRINTF1("Device Name = %s", szPort); // Open the modem port // hComm = CreateFile(szPort, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (hComm == INVALID_HANDLE_VALUE) { dwRet = GetLastError(); TSPPRINTF1("hComm CreateFile() failed! (%d)", dwRet); return dwRet; }; if (!PurgeComm(hComm, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR) ) { dwRet = GetLastError(); CloseHandle(hComm); return dwRet; } ASSERT(ghCompletionPort != NULL); if (CreateIoCompletionPort(hComm, ghCompletionPort, (DWORD)pLineDev, 0) == NULL) { dwRet = GetLastError(); CloseHandle(hComm); return dwRet; } SetupComm (hComm, 4096, 4096); // Open Mcx handle // if ((dwRet = MCXOpen(pLineDev->szDeviceName, hComm, pLineDev->szDriverKey, &pLineDev->hModem, pLineDev->dwID, (DWORD)pLineDev)) == ERROR_SUCCESS) { pLineDev->hDevice = hComm; // Set the modem configuration // UnimodemSetCommConfig(pLineDev, (LPCOMMCONFIG)lpComConfig, dwSize); } else { CloseHandle(hComm); }; return (MapMcxResult(dwRet)); } //**************************************************************************** // DWORD CloseModem (PLINEDEV) // // Function: Opens the modem device. // // Notes: This function never returns success synchrnously // // Returns: ERROR_IO_PENDING if the operation will complete asynchronously // an error code for synchrnous failure // // Fri 14-Apr-1995 12:47:26 -by- Viroon Touranachun [viroont] // created //**************************************************************************** DWORD CloseModem (PLINEDEV pLineDev) { TSPPRINTF("Close modem"); // // close comm handle as well // MCXClose( pLineDev->hModem, pLineDev->hDevice, pLineDev->LineClosed ); pLineDev->hModem = NULL; pLineDev->hDevice = INVALID_DEVICE; return ERROR_SUCCESS; } //**************************************************************************** // DWORD UnimodemInit (PLINEDEV) // // Function: Initializes the modem device. // // Notes: This function never returns success synchrnously // // Returns: ERROR_IO_PENDING if the operation will complete asynchronously // an error code for synchrnous failure // // Fri 14-Apr-1995 12:47:26 -by- Viroon Touranachun [viroont] // created //**************************************************************************** DWORD UnimodemInit (PLINEDEV pLineDev) { MCX_IN mcxi; DWORD dwRet; LPCOMMCONFIG lpCommConfig; lpCommConfig = (LPCOMMCONFIG)&(pLineDev->pDevCfg->commconfig); // set the new configuration // UnimodemSetCommConfig(pLineDev, lpCommConfig, lpCommConfig->dwSize); // Prepare the input/output information // pLineDev->dwVxdPendingID++; mcxi.dwReqID = pLineDev->dwVxdPendingID; mcxi.pMcxOut = &pLineDev->McxOut; pLineDev->McxOut.dwReqID = mcxi.dwReqID; pLineDev->McxOut.dwResult = MDM_FAILURE; TSPPRINTF1("UnimodemInit id: %d", pLineDev->dwVxdPendingID); pLineDev->InitStringsAreValid=TRUE; dwRet = MCXInit(pLineDev->hModem, &mcxi); dwRet = MapMcxResult(dwRet); // If the MCX call returns success, converts it to async success // if (dwRet == ERROR_SUCCESS) { // The operation completes successfully synchronously // dwRet = ERROR_IO_PENDING; pLineDev->McxOut.dwResult = MDM_SUCCESS; PostQueuedCompletionStatus(ghCompletionPort, CP_BYTES_WRITTEN(0), (DWORD)pLineDev, NULL); }; return dwRet; } //**************************************************************************** // DWORD UnimodemDial (PLINEDEV, LPSTR) // // Function: dials the modem device with the provided dialable string. A dial- // able string can be: // "" - originate // ";" - dialtone detection // "5551212" - dial and originate // "5551212;" - dial // // Notes: This function never returns success synchrnously // // Returns: ERROR_IO_PENDING if the operation will complete asynchronously // an error code for synchrnous failure // // Fri 14-Apr-1995 12:47:26 -by- Viroon Touranachun [viroont] // created //**************************************************************************** DWORD UnimodemDial( PLINEDEV pLineDev, LPSTR szAddress, DWORD DialOptions ) { MCX_IN mcxi; DWORD dwRet; char szPhone[MAXADDRESSLEN+1]; // Prepare the input/output information // pLineDev->dwVxdPendingID++; mcxi.dwReqID = pLineDev->dwVxdPendingID; mcxi.pMcxOut = &pLineDev->McxOut; pLineDev->McxOut.dwReqID = mcxi.dwReqID; pLineDev->McxOut.dwResult = MDM_FAILURE; lstrcpyA(szPhone, szAddress); TSPPRINTF1("UnmodemDial id: %d", pLineDev->dwVxdPendingID); dwRet = MCXDial(pLineDev->hModem, szPhone, &mcxi, DialOptions); dwRet = MapMcxResult(dwRet); // If the MCX call returns success, converts it to async success // if (dwRet == ERROR_SUCCESS) { // The operation completes successfully synchronously // dwRet = ERROR_IO_PENDING; pLineDev->McxOut.dwResult = MDM_SUCCESS; PostQueuedCompletionStatus(ghCompletionPort, CP_BYTES_WRITTEN(0), (DWORD)pLineDev, NULL); }; return dwRet; } //**************************************************************************** // DWORD UnimodemMonitor (PLINEDEV, DWORD) // // Function: Monitors the modem for an incoming call in two modes: // MONITOR_NON_CONTINUOUS - Notify the first ring only // MONITOR_CONTINUOUS - Notify each ring // // Notes: This function never returns success synchrnously // // Returns: ERROR_IO_PENDING if the operation will complete asynchronously // an error code for synchrnous failure // // Fri 14-Apr-1995 12:47:26 -by- Viroon Touranachun [viroont] // created //**************************************************************************** DWORD UnimodemMonitor (PLINEDEV pLineDev, DWORD dwType) { MCX_IN mcxi; DWORD dwRet; // Prepare the input/output information // pLineDev->dwVxdPendingID++; mcxi.dwReqID = pLineDev->dwVxdPendingID; mcxi.pMcxOut = &pLineDev->McxOut; pLineDev->McxOut.dwReqID = mcxi.dwReqID; pLineDev->McxOut.dwResult = MDM_FAILURE; TSPPRINTF1("UnmodemMonitor id: %d", pLineDev->dwVxdPendingID); dwRet = MCXMonitor(pLineDev->hModem, dwType, &mcxi); dwRet = MapMcxResult(dwRet); // If the MCX call returns success, converts it to async success // if (dwRet == ERROR_SUCCESS) { // The operation completes successfully synchronously // dwRet = ERROR_IO_PENDING; pLineDev->McxOut.dwResult = MDM_SUCCESS; PostQueuedCompletionStatus(ghCompletionPort, CP_BYTES_WRITTEN(0), (DWORD)pLineDev, NULL); }; return dwRet; } //**************************************************************************** // DWORD UnimodemAnswer (PLINEDEV) // // Function: Answers the incoming call.. // // Notes: This function never returns success synchrnously // // Returns: ERROR_IO_PENDING if the operation will complete asynchronously // an error code for synchrnous failure // // Fri 14-Apr-1995 12:47:26 -by- Viroon Touranachun [viroont] // created //**************************************************************************** DWORD UnimodemAnswer (PLINEDEV pLineDev) { MCX_IN mcxi; DWORD dwRet; // Prepare the input/output information // pLineDev->dwVxdPendingID++; mcxi.dwReqID = pLineDev->dwVxdPendingID; mcxi.pMcxOut = &pLineDev->McxOut; pLineDev->McxOut.dwReqID = mcxi.dwReqID; pLineDev->McxOut.dwResult = MDM_FAILURE; TSPPRINTF1("UnmodemAnswer id: %d", pLineDev->dwVxdPendingID); dwRet = MCXAnswer(pLineDev->hModem, &mcxi); dwRet = MapMcxResult(dwRet); // If the MCX call returns success, converts it to async success // if (dwRet == ERROR_SUCCESS) { // The operation completes successfully synchronously // dwRet = ERROR_IO_PENDING; pLineDev->McxOut.dwResult = MDM_SUCCESS; PostQueuedCompletionStatus(ghCompletionPort, CP_BYTES_WRITTEN(0), (DWORD)pLineDev, NULL); }; return dwRet; } VOID WINAPI DisconnectHandler( PLINEDEV pLineDev ) { TSPPRINTF("DisconnectHandle:"); NEW_CALLSTATE(pLineDev, LINECALLSTATE_DISCONNECTED, LINEDISCONNECTMODE_NORMAL); return; } //**************************************************************************** // DWORD UnimodemMonitorDisconnect (PLINEDEV) // // Function: Monitors the remote disconnection. When the remote disconnection // occurs, the function completes successfully in the async thread. // // Notes: This function never returns success synchrnously // // Returns: ERROR_IO_PENDING if the operation will complete asynchronously // an error code for synchrnous failure // // Fri 14-Apr-1995 12:47:26 -by- Viroon Touranachun [viroont] // created //**************************************************************************** DWORD UnimodemMonitorDisconnect (PLINEDEV pLineDev) { DWORD Result; Result=McxRegisterDisconectHandler( pLineDev->hModem, DisconnectHandler, pLineDev ); return Result; } //**************************************************************************** // DWORD UnimodemCancelMonitorDisconnect (PLINEDEV) // // Function: Cancel the pending monitoring remote disconnection request. // The async thread always ignore the cancellation result. // // Notes: This function is synchronous. // // Returns: ERROR_SUCCESS if success // an error code for failure // // Fri 14-Apr-1995 12:47:26 -by- Viroon Touranachun [viroont] // created //**************************************************************************** DWORD UnimodemCancelMonitorDisconnect (PLINEDEV pLineDev) { DWORD dwRet; dwRet=McxDeregisterDisconnectHandler( pLineDev->hModem ); return dwRet; } //**************************************************************************** // DWORD UnimodemHangup (PLINEDEV, BOOL) // // Function: Disconnect the modem locally. The caller can specifies whether // the function should complete synchrnously or asynchronously. // // Returns: ERROR_SUCCESS if success synchronously. // ERROR_IO_PENDING if the operation will complete asynchronously // an error code for failure // // Fri 14-Apr-1995 12:47:26 -by- Viroon Touranachun [viroont] // created //**************************************************************************** DWORD UnimodemHangup (PLINEDEV pLineDev, BOOL fSync) { MCX_IN mcxi; DWORD dwRet; if(!fSync) { // Asynchronous request, use the default event // pLineDev->dwVxdPendingID++; TSPPRINTF1("UnmodemAsyncHangup id: %d", pLineDev->dwVxdPendingID); } else { // Synchronous request, create a local event so we can wait for it here // if ((pLineDev->hSynchronizeEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL) { return ERROR_OUTOFMEMORY; }; TSPPRINTF("UnmodemSyncHangup"); }; // Prepare the input/output information // mcxi.dwReqID = pLineDev->dwVxdPendingID; mcxi.pMcxOut = &pLineDev->McxOut; pLineDev->McxOut.dwReqID = mcxi.dwReqID; pLineDev->McxOut.dwResult = MDM_FAILURE; // Hang up the line // dwRet = MCXHangup(pLineDev->hModem, &mcxi); dwRet = MapMcxResult(dwRet); switch(dwRet) { case ERROR_SUCCESS: // // The operation completes successfully synchronously // pLineDev->McxOut.dwResult = MDM_SUCCESS; // If the operation is an async request, handles the result asynchronously // if (fSync) { TSPPRINTF("UnmodemSyncHangup completes"); CloseHandle(pLineDev->hSynchronizeEvent); pLineDev->hSynchronizeEvent = NULL; } else { dwRet = ERROR_IO_PENDING; PostQueuedCompletionStatus(ghCompletionPort, CP_BYTES_WRITTEN(0), (DWORD)pLineDev, NULL); }; break; case ERROR_IO_PENDING: // // If it is synchronous request, need to wait until it is done // if (fSync) { RELEASE_LINEDEV(pLineDev); WaitForSingleObject(pLineDev->hSynchronizeEvent, INFINITE); CLAIM_LINEDEV(pLineDev); CloseHandle(pLineDev->hSynchronizeEvent); pLineDev->hSynchronizeEvent = NULL; dwRet = ERROR_SUCCESS; }; break; default: break; }; return dwRet; } //**************************************************************************** // DWORD UnimodemGetCommConfig (PLINEDEV, LPCOMMCONFIG, LPDWORD) // // Function: Gets the modem comm configuration // // Notes: This function is synchronous. // // Returns: ERROR_SUCCESS if success // an error code for failure // // Fri 14-Apr-1995 12:47:26 -by- Viroon Touranachun [viroont] // created //**************************************************************************** DWORD UnimodemGetCommConfig (PLINEDEV pLineDev, LPCOMMCONFIG lpCommConfig, LPDWORD lpcb) { DWORD dwRet; TSPPRINTF("UnimodemGetCommConfig."); dwRet = MCXGetCommConfig(pLineDev->hModem, lpCommConfig, lpcb); return (MapMcxResult(dwRet)); } //**************************************************************************** // DWORD UnimodemSetCommConfig (PLINEDEV, LPCOMMCONFIG, DWORD) // // Function: Sets the modem comm configuration // // Notes: This function is synchronous. // // Returns: ERROR_SUCCESS if success // an error code for failure // // Fri 14-Apr-1995 12:47:26 -by- Viroon Touranachun [viroont] // created //**************************************************************************** DWORD UnimodemSetCommConfig (PLINEDEV pLineDev, LPCOMMCONFIG lpCommConfig, DWORD cb) { DWORD dwRet; TSPPRINTF("UnimodemSetCommConfig."); dwRet = MCXSetCommConfig(pLineDev->hModem, lpCommConfig, cb); return (MapMcxResult(dwRet)); } #if 0 //**************************************************************************** // DWORD UnimodemSetCommConfig (PLINEDEV, LPCOMMCONFIG, DWORD) // // Function: Sets the modem comm configuration // // Notes: This function is synchronous. // // Returns: ERROR_SUCCESS if success // an error code for failure // // Fri 14-Apr-1995 12:47:26 -by- Viroon Touranachun [viroont] // created //**************************************************************************** DWORD WINAPI UnimodemSetModemSettings( PLINEDEV pLineDev, LPMODEMSETTINGS lpModemSettings ) { DWORD dwRet; TSPPRINTF("UnimodemSetModemSettings."); dwRet = MCXSetModemSettings(pLineDev->hModem, lpModemSettings); return (MapMcxResult(dwRet)); } #endif //**************************************************************************** // DWORD UnimodemSetPassthrough (PLINEDEV, DWORD) // // Function: Sets the modem passthrough mode to: // PASSTHROUGH_ON // PASSTHROUGH_OFF // PASSTHROUGH_OFF_BUT_CONNECTED // // Notes: This function is synchronous. // // Returns: ERROR_SUCCESS if success // an error code for failure // // Fri 14-Apr-1995 12:47:26 -by- Viroon Touranachun [viroont] // created //**************************************************************************** DWORD UnimodemSetPassthrough (PLINEDEV pLineDev, DWORD dwMode) { DWORD dwRet; TSPPRINTF1("UnimodemSetPassthrough mode: %d", dwMode); dwRet = MCXSetPassthrough(pLineDev->hModem, dwMode); return (MapMcxResult(dwRet)); } //**************************************************************************** // DWORD UnimodemGetNegotiatedRate (PLINEDEV, LPDWORD) // // Function: Gets the modem connection speed // // Notes: This function is synchronous. // // Returns: ERROR_SUCCESS if success // an error code for failure // // Fri 14-Apr-1995 12:47:26 -by- Viroon Touranachun [viroont] // created //**************************************************************************** DWORD UnimodemGetNegotiatedRate (PLINEDEV pLineDev, LPDWORD lpdwRate) { DWORD dwRet; dwRet = MCXGetNegotiatedRate(pLineDev->hModem, lpdwRate); return (MapMcxResult(dwRet)); }