windows-nt/Source/XPSP1/NT/ds/netapi/svcdlls/upssvc/upsexe/gnrcups.c
2020-09-26 16:20:57 +08:00

727 lines
17 KiB
C

/* Copyright 1999 American Power Conversion, All Rights Reserved
*
* Description:
* Implements the generic UPS
*
* Revision History:
* mholly 19Apr1999 initial revision.
* mholly 21Apr1999 hold shutoff pin for 5 sec instead of 3
* mholly 12May1999 UPSInit no longer takes the comm port param
*
*/
#include <tchar.h>
#include <windows.h>
#include "gnrcups.h"
#include "upsreg.h"
//
// provide meaningful names to the comm port pins
//
#define LINE_FAIL MS_CTS_ON
#define LOW_BATT MS_RLSD_ON
#define LINE_FAIL_MASK EV_CTS
#define LOW_BATT_MASK EV_RLSD
// Amount of time to wait in order for the port to settle down
#define DEBOUNCE_DELAY_TIME 250
//
// private functions used in the implementation of the
// public Generic UPS funtions
//
static DWORD openCommPort(LPCTSTR aCommPort);
static DWORD setupCommPort(DWORD aSignalsMask);
static DWORD getSignalsMask(void);
static void updateUpsState(DWORD aModemStatus);
static BOOL upsLineAsserted(DWORD ModemStatus, DWORD Line);
static DWORD startUpsMonitoring(void);
static DWORD WINAPI UpsMonitoringThread(LPVOID unused);
//
// UPSDRIVERCONTEXT
//
// provides a framework to encapsulate data that is
// shared among the functions in this file
//
struct UPSDRIVERCONTEXT
{
HANDLE theCommPort;
DWORD theState;
HANDLE theStateChangedEvent;
DWORD theSignalsMask;
HANDLE theMonitoringThreadHandle;
HANDLE theStopMonitoringEvent;
};
//
// _theUps
//
// provides a single instance of a UPSDRIVERCONTEXT
// structure that all functions in this file will use
//
static struct UPSDRIVERCONTEXT _theUps;
/**
* GenericUPSInit
*
* Description:
* Retrieves the UPS signalling information from the
* NT Registry and attempts to open the comm port and
* configure it as defined by the signalling data.
* Also creates a inter-thread signal, theStateChangedEvent,
* and starts the monitoring of the UPS on a separate thread
* via a call to startUpsMonitoring.
* The GenericUPSInit function must be called before any
* other function in this file
*
* Parameters:
* None
*
* Returns:
* UPS_INITOK: Initalization was successful
* UPS_INITREGISTRYERROR: The 'Options' registry value is corrupt
* UPS_INITCOMMOPENERROR: The comm port could not be opened
* UPS_INITCOMMSETUPERROR: The comm port could not be configured
* UPS_INITUNKNOWNERROR: Undefined error has occurred
*
*/
DWORD GenericUPSInit(void)
{
DWORD init_err = UPS_INITOK;
TCHAR comm_port[MAX_PATH];
_theUps.theStateChangedEvent = NULL;
_theUps.theState = UPS_ONLINE;
_theUps.theCommPort = NULL;
_theUps.theMonitoringThreadHandle = NULL;
_theUps.theStopMonitoringEvent = NULL;
if (ERROR_SUCCESS != getSignalsMask()) {
init_err = UPS_INITREGISTRYERROR;
goto init_end;
}
//
// Initialize registry functions
//
InitUPSConfigBlock();
//
// get the comm port to use
//
if (ERROR_SUCCESS != GetUPSConfigPort(comm_port)) {
init_err = UPS_INITREGISTRYERROR;
goto init_end;
}
if (ERROR_SUCCESS != openCommPort(comm_port)) {
init_err = UPS_INITCOMMOPENERROR;
goto init_end;
}
_theUps.theStateChangedEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!_theUps.theStateChangedEvent) {
init_err = UPS_INITUNKNOWNERROR;
goto init_end;
}
if (ERROR_SUCCESS != setupCommPort(_theUps.theSignalsMask)) {
init_err = UPS_INITCOMMSETUPERROR;
goto init_end;
}
if (ERROR_SUCCESS != startUpsMonitoring()) {
init_err = UPS_INITUNKNOWNERROR;
goto init_end;
}
init_end:
if (UPS_INITOK != init_err) {
GenericUPSStop();
}
return init_err;
}
/**
* GenericUPSStop
*
* Description:
* calls GenericUPSCancelWait to release pending threads
* Stops the thread that is monitoring the UPS
* Closes the comm port
* Resets all data to default values
* After a call to GenericUPSStop, only the GenericUPSInit
* function is valid
*
* Parameters:
* None
*
* Returns:
* None
*
*/
void GenericUPSStop(void)
{
GenericUPSCancelWait();
if (_theUps.theStopMonitoringEvent) {
SetEvent(_theUps.theStopMonitoringEvent);
}
if (_theUps.theMonitoringThreadHandle) {
WaitForSingleObject(_theUps.theMonitoringThreadHandle, INFINITE);
CloseHandle(_theUps.theMonitoringThreadHandle);
_theUps.theMonitoringThreadHandle = NULL;
}
if (_theUps.theStopMonitoringEvent) {
CloseHandle(_theUps.theStopMonitoringEvent);
_theUps.theStopMonitoringEvent = NULL;
}
if (_theUps.theCommPort) {
CloseHandle(_theUps.theCommPort);
_theUps.theCommPort = NULL;
}
if (_theUps.theStateChangedEvent) {
CloseHandle(_theUps.theStateChangedEvent);
_theUps.theStateChangedEvent = NULL;
}
_theUps.theState = UPS_ONLINE;
_theUps.theSignalsMask = 0;
}
/**
* GenericUPSWaitForStateChange
*
* Description:
* Blocks until the state of the UPS differs
* from the value passed in via aState or
* anInterval milliseconds has expired. If
* anInterval has a value of INFINITE this
* function will never timeout
*
* Parameters:
* aState: defines the state to wait for a change from,
* possible values:
* UPS_ONLINE
* UPS_ONBATTERY
* UPS_LOWBATTERY
* UPS_NOCOMM
*
* anInterval: timeout in milliseconds, or INFINITE for
* no timeout interval
*
* Returns:
* None
*
*/
void GenericUPSWaitForStateChange(DWORD aLastState, DWORD anInterval)
{
if (aLastState == _theUps.theState) {
//
// wait for a state change from the UPS
//
if (_theUps.theStateChangedEvent) {
WaitForSingleObject(_theUps.theStateChangedEvent, anInterval);
}
}
}
/**
* GenericUPSGetState
*
* Description:
* returns the current state of the UPS
*
* Parameters:
* None
*
* Returns:
* possible values:
* UPS_ONLINE
* UPS_ONBATTERY
* UPS_LOWBATTERY
* UPS_NOCOMM
*
*/
DWORD GenericUPSGetState(void)
{
return _theUps.theState;
}
/**
* GenericUPSCancelWait
*
* Description:
* interrupts pending calls to GenericUPSWaitForStateChange
* without regard to timout or state change
*
* Parameters:
* None
*
* Returns:
* None
*
*/
void GenericUPSCancelWait(void)
{
if (_theUps.theStateChangedEvent) {
SetEvent(_theUps.theStateChangedEvent);
}
}
/**
* GenericUPSTurnOff
*
* Description:
* Attempts to turnoff the UPS after the specified delay.
* Simple signaling UPS do not support this feature, so
* this function does nothing.
*
* Parameters:
* aTurnOffDelay: the minimum amount of time to wait before
* turning off the outlets on the UPS
*
* Returns:
* None
*
*/
void GenericUPSTurnOff(DWORD aTurnOffDelay)
{
// UPS turn off is not supported in simple mode, do nothing
}
/**
* getSignalsMask
*
* Description:
* Queries the registry for the 'Options' value
* The 'Options' value defines a bitmask that is
* used to configure the comm port settings
*
* Parameters:
* None
*
* Returns:
* ERROR_SUCCESS: signals mask retrieved OK
* any other return code indicates failure to
* retrieve the value from the registry
*
*/
DWORD getSignalsMask(void)
{
DWORD status = ERROR_SUCCESS;
DWORD value = 0;
status = GetUPSConfigOptions(&value);
if (ERROR_SUCCESS == status) {
_theUps.theSignalsMask = value;
}
else {
_theUps.theSignalsMask = 0;
}
return status;
}
/**
* openCommPort
*
* Description:
* Attempts to open the comm port
*
* Parameters:
* aCommPort: indicates which comm port
* on the system to open
*
* Returns:
* ERROR_SUCCESS: comm port opened OK
* any other return code indicates failure to
* open the comm port - the exact error code
* is set by the CreateFile function
*
*/
DWORD openCommPort(LPCTSTR aCommPort)
{
DWORD err = ERROR_SUCCESS;
_theUps.theCommPort = CreateFile(
aCommPort,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL
);
if (_theUps.theCommPort == INVALID_HANDLE_VALUE) {
err = GetLastError();
}
return err;
}
/**
* setupCommPort
*
* Description:
* Attempts to setup the comm port - this method
* initializes the shutoff pin to an unsignalled
* state, and tells the system what other pins
* it should monitor for changes
*
* Parameters:
* aSignalsMask: defines a bitmask that will
* be used to configure the
* comm port
*
* Returns:
* ERROR_SUCCESS: comm port setup OK
* any other return code indicates failure to
* setup the comm port - the exact error code
* is set by the EscapeCommFunction function
*
*/
DWORD setupCommPort(DWORD aSignalsMask)
{
DWORD err = ERROR_SUCCESS;
DWORD ModemStatus = 0;
DWORD UpsActiveSignals = 0;
DWORD UpsCommMask = 0;
//
// first set the 'shutoff' pin to the
// unsignaled state, don't want to
// shutoff the UPS now...
//
if (aSignalsMask & UPS_POSSIGSHUTOFF) {
ModemStatus = CLRDTR;
}
else {
ModemStatus = SETDTR;
}
if (!EscapeCommFunction(_theUps.theCommPort, ModemStatus)) {
err = GetLastError();
}
if (!EscapeCommFunction(_theUps.theCommPort, SETRTS)) {
err = GetLastError();
}
if (!EscapeCommFunction(_theUps.theCommPort, SETXOFF)) {
err = GetLastError();
}
//
// determine what pins should be monitored for activity
//
UpsActiveSignals =
(aSignalsMask & ( UPS_POWERFAILSIGNAL | UPS_LOWBATTERYSIGNAL));
switch (UpsActiveSignals) {
case UPS_POWERFAILSIGNAL:
UpsCommMask = LINE_FAIL_MASK;
break;
case UPS_LOWBATTERYSIGNAL:
UpsCommMask = LOW_BATT_MASK;
break;
case (UPS_LOWBATTERYSIGNAL | UPS_POWERFAILSIGNAL):
UpsCommMask = (LINE_FAIL_MASK | LOW_BATT_MASK);
break;
}
//
// tell the system what pins we are interested in
// monitoring activity
//
if (!SetCommMask(_theUps.theCommPort, UpsCommMask)) {
err = GetLastError();
}
//
// simply wait for 3 seconds for the pins to 'settle',
// failure to do so results in misleading status to
// be returned from GetCommModemStatus
//
WaitForSingleObject(_theUps.theStateChangedEvent, 3000);
GetCommModemStatus( _theUps.theCommPort, &ModemStatus);
updateUpsState(ModemStatus);
return err;
}
/**
* startUpsMonitoring
*
* Description:
* Creates a separate thread to perform the monitoring
* of the comm port to which the UPS is connected
* Also creates an event that other threads can signal
* to indicate that the monitoring thread should exit
*
* Parameters:
* None
*
* Returns:
* ERROR_SUCCESS: thread creation OK
* any other return code indicates failure to
* start the thread, either the thread was not
* created or the stop event was not created
*
*/
DWORD startUpsMonitoring()
{
DWORD err = ERROR_SUCCESS;
DWORD thread_id = 0;
_theUps.theStopMonitoringEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!_theUps.theStopMonitoringEvent) {
err = GetLastError();
}
else {
_theUps.theMonitoringThreadHandle =
CreateThread(NULL, 0, UpsMonitoringThread, NULL, 0, &thread_id);
if (!_theUps.theMonitoringThreadHandle) {
err = GetLastError();
}
}
return err;
}
/**
* updateUpsState
*
* Description:
* Determines the state of the UPS based upon
* the state of the line fail and low battery
* pins of the UPS
* If the state of the UPS changes, then this
* method will signal theStateChangedEvent, this
* in-turn will release any threads waiting in
* the GenericUPSWaitForStateChange function
*
* Parameters:
* aModemStatus: a bitmask that represents the
* state of the comm port pins, this
* value should be retrieved by a call
* to GetCommModemStatus
*
* Returns:
* None
*
*/
void updateUpsState(DWORD aModemStatus)
{
DWORD old_state = _theUps.theState;
if (upsLineAsserted(aModemStatus, LINE_FAIL)) {
if (upsLineAsserted(aModemStatus, LOW_BATT)) {
_theUps.theState = UPS_LOWBATTERY;
}
else {
_theUps.theState = UPS_ONBATTERY;
}
}
else {
_theUps.theState = UPS_ONLINE;
}
if (old_state != _theUps.theState) {
SetEvent(_theUps.theStateChangedEvent);
}
}
/**
* upsLineAsserted
*
* Description:
* Determines if the signal, either LINE_FAIL or LOW_BATT,
* is asserted or not. The line is asserted based on the
* voltage levels set in _theUps.theSignalsMask.
* The aModemStatus bitmask signals a positive voltage
* by a value of 1.
* Below is a chart showing the logic used in determining
* if a line is asserted or not
*
* --------------------------------------------------------------
* | UPS positive signals | UPS negative signals
* --------------------------------------------------------------
* line positive | asserted | not asserted
* ---------------|-----------------------|----------------------
* line negative | not asserted | asserted
* ---------------|-----------------------|----------------------
*
* Parameters:
* aModemStatus: a bitmask that represents the
* state of the comm port pins - the value
* should be retrieved by a call to
* GetCommModemStatus
* Line: either LINE_FAIL or LOW_BATT
*
* Returns:
* TRUE if line is asserted, FALSE otherwise
*
*/
BOOL upsLineAsserted(DWORD aModemStatus, DWORD aLine)
{
DWORD asserted;
DWORD status;
DWORD assertion;
//
// only look at the line that was selected
// this filters out the other fields in the
// aModemStatus bitmask
//
status = aLine & aModemStatus;
//
// determine if the line is asserted based
// on positive or negative voltages
//
assertion = (aLine == LINE_FAIL) ?
(_theUps.theSignalsMask & UPS_POSSIGONPOWERFAIL) :
(_theUps.theSignalsMask & UPS_POSSIGONLOWBATTERY);
if (status) {
//
// the line has positive voltage
//
if (assertion) {
//
// the UPS uses positive voltage to
// assert the line
//
asserted = TRUE;
}
else {
//
// the UPS uses negative voltage to
// assert the line
//
asserted = FALSE;
}
}
else {
//
// the line has negative voltage
//
if (assertion) {
//
// the UPS uses positive voltage to
// assert the line
//
asserted = FALSE;
}
else {
//
// the UPS uses negative voltage to
// assert the line
//
asserted = TRUE;
}
}
return asserted;
}
/**
* UpsMonitoringThread
*
* Description:
* Method used by the thread to monitor the UPS port
* for changes. The thread will exit when the event
* _theUps.theStopMonitoringEvent is signalled
*
* Parameters:
* unused: not used
*
* Returns:
* ERROR_SUCCESS
*
*/
DWORD WINAPI UpsMonitoringThread(LPVOID unused)
{
DWORD ModemStatus = 0;
HANDLE events[2];
OVERLAPPED UpsPinOverlap;
//
// create an event for the OVERLAPPED struct, this event
// will signalled when one of the pins that we are
// monitoring, defined by SetCommMask in setupCommPort,
// indicates a change in its signal state
//
UpsPinOverlap.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
//
// since the thread reacts to two events, a signal from the
// comm port, and a Stop event, we initialize an array of events
// to pass into WaitForMultipleObjects
//
events[0] = _theUps.theStopMonitoringEvent;
events[1] = UpsPinOverlap.hEvent;
while (TRUE) {
//
// tell the system to wait for comm events again
//
WaitCommEvent(_theUps.theCommPort, &ModemStatus, &UpsPinOverlap);
//
// block waiting for either a comm port event, or a stop
// request from another thread
//
WaitForMultipleObjects(2, events, FALSE, INFINITE);
//
// Test to see if the stop event is signalled, if it
// is then break out of the while loop.
//
// The wait is to allow for the port to settle before reading
// the value.
//
if (WAIT_OBJECT_0 ==
WaitForSingleObject(_theUps.theStopMonitoringEvent, DEBOUNCE_DELAY_TIME)) {
break;
}
//
// ask the system about the status of the comm port
// and pass the value to updateUpsState so it can
// determine the new state of the UPS
// Then simply continue monitoring the port.
//
GetCommModemStatus(_theUps.theCommPort, &ModemStatus);
updateUpsState(ModemStatus);
}
CloseHandle(UpsPinOverlap.hEvent);
return ERROR_SUCCESS;
}