summaryrefslogtreecommitdiff
path: root/usblib/host/usbhhid.c
diff options
context:
space:
mode:
authorYuval Adam <yuv.adm@gmail.com>2014-03-16 14:41:11 +0200
committerYuval Adam <yuv.adm@gmail.com>2014-03-16 14:41:11 +0200
commit990090a4cc9070837d31e66b58d40f0c3d038741 (patch)
treecf1b905082c364e9b223e0c5058566103138dae5 /usblib/host/usbhhid.c
parent7f4da522479c0f00126219f0c23b804c3a93d7a6 (diff)
Add usblib and utils
Diffstat (limited to 'usblib/host/usbhhid.c')
-rw-r--r--usblib/host/usbhhid.c746
1 files changed, 746 insertions, 0 deletions
diff --git a/usblib/host/usbhhid.c b/usblib/host/usbhhid.c
new file mode 100644
index 0000000..bfbea85
--- /dev/null
+++ b/usblib/host/usbhhid.c
@@ -0,0 +1,746 @@
+//*****************************************************************************
+//
+// usbhhid.c - This file contains the host HID driver.
+//
+// Copyright (c) 2008-2014 Texas Instruments Incorporated. All rights reserved.
+// Software License Agreement
+//
+// Texas Instruments (TI) is supplying this software for use solely and
+// exclusively on TI's microcontroller products. The software is owned by
+// TI and/or its suppliers, and is protected under applicable copyright
+// laws. You may not combine this software with "viral" open-source
+// software in order to form a larger program.
+//
+// THIS SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS.
+// NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT
+// NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. TI SHALL NOT, UNDER ANY
+// CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
+// DAMAGES, FOR ANY REASON WHATSOEVER.
+//
+// This is part of revision 2.1.0.12573 of the Tiva USB Library.
+//
+//*****************************************************************************
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "inc/hw_types.h"
+#include "driverlib/usb.h"
+#include "usblib/usblib.h"
+#include "usblib/usblibpriv.h"
+#include "usblib/usbhid.h"
+#include "usblib/host/usbhost.h"
+#include "usblib/host/usbhostpriv.h"
+#include "usblib/host/usbhhid.h"
+
+static void * HIDDriverOpen(tUSBHostDevice *psDevice);
+static void HIDDriverClose(void *pvInstance);
+
+//*****************************************************************************
+//
+//! \addtogroup usblib_host_class
+//! @{
+//
+//*****************************************************************************
+
+//*****************************************************************************
+//
+// If the user has not explicitly stated the maximum number of HID devices to
+// support, we assume that we need to support up to the maximum number of USB
+// devices that the build is configured for.
+//
+//*****************************************************************************
+#ifndef MAX_HID_DEVICES
+#define MAX_HID_DEVICES MAX_USB_DEVICES
+#endif
+
+//*****************************************************************************
+//
+// This is the structure that holds all of the data for a given instance of
+// a HID device.
+//
+//*****************************************************************************
+struct tHIDInstance
+{
+ //
+ // Save the device instance.
+ //
+ tUSBHostDevice *psDevice;
+
+ //
+ // Used to save the callback.
+ //
+ tUSBCallback pfnCallback;
+
+ //
+ // Callback data provided by caller.
+ //
+ void *pvCBData;
+
+ //
+ // Used to remember what type of device was registered.
+ //
+ tHIDSubClassProtocol iDeviceType;
+
+ //
+ // Interrupt IN pipe.
+ //
+ uint32_t ui32IntInPipe;
+};
+
+//*****************************************************************************
+//
+// The instance data storage for attached hid devices.
+//
+//*****************************************************************************
+static tHIDInstance g_psHIDDevice[MAX_HID_DEVICES];
+
+//*****************************************************************************
+//
+//! This constant global structure defines the HID Class Driver that is
+//! provided with the USB library.
+//
+//*****************************************************************************
+const tUSBHostClassDriver g_sUSBHIDClassDriver =
+{
+ USB_CLASS_HID,
+ HIDDriverOpen,
+ HIDDriverClose,
+ 0
+};
+
+//*****************************************************************************
+//
+//! This function is used to open an instance of a HID device.
+//!
+//! \param iDeviceType is the type of device that should be loaded for this
+//! instance of the HID device.
+//! \param pfnCallback is the function that will be called whenever changes
+//! are detected for this device.
+//! \param pvCBData is the data that will be returned in when the
+//! \e pfnCallback function is called.
+//!
+//! This function creates an instance of an specific type of HID device. The
+//! \e iDeviceType parameter is one subclass/protocol values of the types
+//! specified in enumerated types tHIDSubClassProtocol. Only devices that
+//! enumerate with this type will be called back via the \e pfnCallback
+//! function. The \e pfnCallback parameter is the callback function for any
+//! events that occur for this device type. The \e pfnCallback function must
+//! point to a valid function of type \e tUSBCallback for this call to complete
+//! successfully. To release this device instance the caller of USBHHIDOpen()
+//! should call USBHHIDClose() and pass in the value returned from the
+//! USBHHIDOpen() call.
+//!
+//! \return This function returns and instance value that should be used with
+//! any other APIs that require an instance value. If a value of 0 is returned
+//! then the device instance could not be created.
+//
+//*****************************************************************************
+tHIDInstance *
+USBHHIDOpen(tHIDSubClassProtocol iDeviceType, tUSBCallback pfnCallback,
+ void *pvCBData)
+{
+ uint32_t ui32Loop;
+
+ //
+ // Find a free device instance structure.
+ //
+ for(ui32Loop = 0; ui32Loop < MAX_HID_DEVICES; ui32Loop++)
+ {
+ if(g_psHIDDevice[ui32Loop].iDeviceType == eUSBHHIDClassNone)
+ {
+ //
+ // Save the instance data for this device.
+ //
+ g_psHIDDevice[ui32Loop].pfnCallback = pfnCallback;
+ g_psHIDDevice[ui32Loop].iDeviceType = iDeviceType;
+ g_psHIDDevice[ui32Loop].pvCBData = pvCBData;
+
+ //
+ // Return the device instance pointer.
+ //
+ return(&g_psHIDDevice[ui32Loop]);
+ }
+ }
+
+ //
+ // If we get here, there are no space device slots so return NULL to
+ // indicate a problem.
+ //
+ return(0);
+}
+
+//*****************************************************************************
+//
+//! This function is used to release an instance of a HID device.
+//!
+//! \param psHIDInstance is the instance value for a HID device to release.
+//!
+//! This function releases an instance of a HID device that was created by a
+//! call to USBHHIDOpen(). This call is required to allow other HID devices
+//! to be enumerated after another HID device has been disconnected. The
+//! \e psHIDInstance parameter should hold the value that was returned from
+//! the previous call to USBHHIDOpen().
+//!
+//! \return None.
+//
+//*****************************************************************************
+void
+USBHHIDClose(tHIDInstance *psHIDInstance)
+{
+ //
+ // Disable any more notifications from the HID layer.
+ //
+ psHIDInstance->pfnCallback = 0;
+
+ //
+ // Mark this device slot as free.
+ //
+ psHIDInstance->iDeviceType = eUSBHHIDClassNone;
+}
+
+//*****************************************************************************
+//
+// This function handles callbacks for the interrupt IN endpoint.
+//
+//*****************************************************************************
+static void
+HIDIntINCallback(uint32_t ui32Pipe, uint32_t ui32Event)
+{
+ int32_t i32Dev;
+
+ switch (ui32Event)
+ {
+ //
+ // Handles a request to schedule a new request on the interrupt IN
+ // pipe.
+ //
+ case USB_EVENT_SCHEDULER:
+ {
+ USBHCDPipeSchedule(ui32Pipe, 0, 1);
+ break;
+ }
+ //
+ // Called when new data is available on the interrupt IN pipe.
+ //
+ case USB_EVENT_RX_AVAILABLE:
+ {
+ //
+ // Determine which device this notification is intended for.
+ //
+ for(i32Dev = 0; i32Dev < MAX_HID_DEVICES; i32Dev++)
+ {
+ //
+ // Does this device own the pipe we have been passed?
+ //
+ if(g_psHIDDevice[i32Dev].ui32IntInPipe == ui32Pipe)
+ {
+ //
+ // Yes - send the report data to the USB host HID device
+ // class driver.
+ //
+ g_psHIDDevice[i32Dev].pfnCallback(
+ g_psHIDDevice[i32Dev].pvCBData,
+ USB_EVENT_RX_AVAILABLE, ui32Pipe, 0);
+ }
+ }
+
+ break;
+ }
+ }
+}
+
+//*****************************************************************************
+//
+//! This function is used to open an instance of the HID driver.
+//!
+//! \param psDevice is a pointer to the device information structure.
+//!
+//! This function will attempt to open an instance of the HID driver based on
+//! the information contained in the psDevice structure. This call can fail if
+//! there are not sufficient resources to open the device. The function will
+//! return a value that should be passed back into USBHIDClose() when the
+//! driver is no longer needed.
+//!
+//! \return The function will return a pointer to a HID driver instance.
+//
+//*****************************************************************************
+static void *
+HIDDriverOpen(tUSBHostDevice *psDevice)
+{
+ int32_t i32Idx, i32Dev;
+ tEndpointDescriptor *psEndpointDescriptor;
+ tInterfaceDescriptor *psInterface;
+
+ //
+ // Get the interface descriptor.
+ //
+ psInterface = USBDescGetInterface(psDevice->psConfigDescriptor, 0, 0);
+
+ //
+ // Search the currently open instances for one that supports the protocol
+ // of this device.
+ //
+ for(i32Dev = 0; i32Dev < MAX_HID_DEVICES; i32Dev++)
+ {
+ if(g_psHIDDevice[i32Dev].iDeviceType ==
+ psInterface->bInterfaceProtocol)
+ {
+ //
+ // Save the device pointer.
+ //
+ g_psHIDDevice[i32Dev].psDevice = psDevice;
+
+ for(i32Idx = 0; i32Idx < 3; i32Idx++)
+ {
+ //
+ // Get the first endpoint descriptor.
+ //
+ psEndpointDescriptor = USBDescGetInterfaceEndpoint(psInterface,
+ i32Idx,
+ 256);
+
+ //
+ // If no more endpoints then break out.
+ //
+ if(psEndpointDescriptor == 0)
+ {
+ break;
+ }
+
+ //
+ // Interrupt
+ //
+ if((psEndpointDescriptor->bmAttributes & USB_EP_ATTR_TYPE_M) ==
+ USB_EP_ATTR_INT)
+ {
+ //
+ // Interrupt IN.
+ //
+ if(psEndpointDescriptor->bEndpointAddress & USB_EP_DESC_IN)
+ {
+ g_psHIDDevice[i32Dev].ui32IntInPipe =
+ USBHCDPipeAlloc(0, USBHCD_PIPE_INTR_IN,
+ psDevice, HIDIntINCallback);
+ USBHCDPipeConfig(g_psHIDDevice[i32Dev].ui32IntInPipe,
+ psEndpointDescriptor->wMaxPacketSize,
+ psEndpointDescriptor->bInterval,
+ (psEndpointDescriptor->bEndpointAddress &
+ USB_EP_DESC_NUM_M));
+ }
+ }
+ }
+
+ //
+ // If there is a callback function call it to inform the application that
+ // the device has been enumerated.
+ //
+ if(g_psHIDDevice[i32Dev].pfnCallback != 0)
+ {
+ g_psHIDDevice[i32Dev].pfnCallback(
+ g_psHIDDevice[i32Dev].pvCBData,
+ USB_EVENT_CONNECTED,
+ (uint32_t)&g_psHIDDevice[i32Dev], 0);
+ }
+
+ //
+ // Save the device pointer.
+ //
+ g_psHIDDevice[i32Dev].psDevice = psDevice;
+
+ return (&g_psHIDDevice[i32Dev]);
+ }
+ }
+
+ //
+ // If we get here, no user has registered an interest in this particular
+ // HID device so we return an error.
+ //
+ return(0);
+}
+
+//*****************************************************************************
+//
+//! This function is used to release an instance of the HID driver.
+//!
+//! \param pvInstance is an instance pointer that needs to be released.
+//!
+//! This function will free up any resources in use by the HID driver instance
+//! that is passed in. The \e pvInstance pointer should be a valid value that
+//! was returned from a call to USBHIDOpen().
+//!
+//! \return None.
+//
+//*****************************************************************************
+static void
+HIDDriverClose(void *pvInstance)
+{
+ tHIDInstance *psInst;
+
+ //
+ // Get our instance pointer.
+ //
+ psInst = (tHIDInstance *)pvInstance;
+
+ //
+ // Reset the device pointer.
+ //
+ psInst->psDevice = 0;
+
+ //
+ // Free the Interrupt IN pipe.
+ //
+ if(psInst->ui32IntInPipe != 0)
+ {
+ USBHCDPipeFree(psInst->ui32IntInPipe);
+ }
+
+ //
+ // If the callback exists, call it with a DISCONNECTED event.
+ //
+ if(psInst->pfnCallback != 0)
+ {
+ psInst->pfnCallback(psInst->pvCBData, USB_EVENT_DISCONNECTED,
+ (uint32_t)pvInstance, 0);
+ }
+}
+
+//*****************************************************************************
+//
+//! This function is used to set the idle timeout for a HID device.
+//!
+//! \param psHIDInstance is the value that was returned from the call to
+//! USBHHIDOpen().
+//! \param ui8Duration is the duration of the timeout in milliseconds.
+//! \param ui8ReportID is the report identifier to set the timeout on.
+//!
+//! This function will send the Set Idle command to a HID device to set the
+//! idle timeout for a given report. The length of the timeout is specified
+//! by the \e ui8Duration parameter and the report the timeout for is in the
+//! \e ui8ReportID value.
+//!
+//! \return Always returns 0.
+//
+//*****************************************************************************
+uint32_t
+USBHHIDSetIdle(tHIDInstance *psHIDInstance, uint8_t ui8Duration,
+ uint8_t ui8ReportID)
+{
+ tUSBRequest sSetupPacket;
+
+ //
+ // This is a Class specific interface OUT request.
+ //
+ sSetupPacket.bmRequestType = USB_RTYPE_DIR_OUT | USB_RTYPE_CLASS |
+ USB_RTYPE_INTERFACE;
+
+ //
+ // Request a Device Descriptor.
+ //
+ sSetupPacket.bRequest = USBREQ_SET_IDLE;
+ sSetupPacket.wValue = (ui8Duration << 8) | ui8ReportID;
+
+ //
+ // Set this on interface 1.
+ //
+ sSetupPacket.wIndex = 0;
+
+ //
+ // This is always 0 for this request.
+ //
+ sSetupPacket.wLength = 0;
+
+ //
+ // Put the setup packet in the buffer.
+ //
+ return(USBHCDControlTransfer(0, &sSetupPacket, psHIDInstance->psDevice,
+ 0, 0, MAX_PACKET_SIZE_EP0));
+}
+
+//*****************************************************************************
+//
+//! This function can be used to retrieve the report descriptor for a given
+//! device instance.
+//!
+//! \param psHIDInstance is the value that was returned from the call to
+//! USBHHIDOpen().
+//! \param pui8Buffer is the memory buffer to use to store the report
+//! descriptor.
+//! \param ui32Size is the size in bytes of the buffer pointed to by
+//! \e pui8Buffer.
+//!
+//! This function is used to return a report descriptor from a HID device
+//! instance so that it can determine how to interpret reports that are
+//! returned from the device indicated by the \e psHIDInstance parameter.
+//! This call is blocking and will return the number of bytes read into the
+//! \e pui8Buffer.
+//!
+//! \return Returns the number of bytes read into the \e pui8Buffer.
+//
+//*****************************************************************************
+uint32_t
+USBHHIDGetReportDescriptor(tHIDInstance *psHIDInstance, uint8_t *pui8Buffer,
+ uint32_t ui32Size)
+{
+ tUSBRequest sSetupPacket;
+ uint32_t ui32Bytes;
+
+ //
+ // This is a Standard Device IN request.
+ //
+ sSetupPacket.bmRequestType = USB_RTYPE_DIR_IN | USB_RTYPE_STANDARD |
+ USB_RTYPE_INTERFACE;
+
+ //
+ // Request a Report Descriptor.
+ //
+ sSetupPacket.bRequest = USBREQ_GET_DESCRIPTOR;
+ sSetupPacket.wValue = USB_HID_DTYPE_REPORT << 8;
+
+ //
+ // Index is always 0 for device requests.
+ //
+ sSetupPacket.wIndex = 0;
+
+ //
+ // All devices must have at least an 8 byte max packet size so just ask
+ // for 8 bytes to start with.
+ //
+ sSetupPacket.wLength = ui32Size;
+
+ //
+ // Now get the full descriptor now that the actual maximum packet size
+ // is known.
+ //
+ ui32Bytes = USBHCDControlTransfer(0, &sSetupPacket,
+ psHIDInstance->psDevice, pui8Buffer, ui32Size,
+ psHIDInstance->psDevice->sDeviceDescriptor.bMaxPacketSize0);
+
+ return(ui32Bytes);
+}
+
+//*****************************************************************************
+//
+//! This function is used to set or clear the boot protocol state of a device.
+//!
+//! \param psHIDInstance is the value that was returned from the call to
+//! USBHHIDOpen().
+//! \param ui32BootProtocol is either zero or non-zero to indicate which
+//! protocol to use for the device.
+//!
+//! A USB host device can use this function to set the protocol for a connected
+//! HID device. This is commonly used to set keyboards and mice into their
+//! simplified boot protocol modes to fix the report structure to a know
+//! state.
+//!
+//! \return This function returns 0.
+//
+//*****************************************************************************
+uint32_t
+USBHHIDSetProtocol(tHIDInstance *psHIDInstance, uint32_t ui32BootProtocol)
+{
+ tUSBRequest sSetupPacket;
+
+ //
+ // This is a Standard Device IN request.
+ //
+ sSetupPacket.bmRequestType = USB_RTYPE_DIR_OUT | USB_RTYPE_CLASS |
+ USB_RTYPE_INTERFACE;
+
+ //
+ // Request a Report Descriptor.
+ //
+ sSetupPacket.bRequest = USBREQ_SET_PROTOCOL;
+
+ if(ui32BootProtocol)
+ {
+ //
+ // Boot Protocol.
+ //
+ sSetupPacket.wValue = 0;
+ }
+ else
+ {
+ //
+ // Report Protocol.
+ //
+ sSetupPacket.wValue = 1;
+ }
+
+ //
+ // Index is always 0 for device requests.
+ //
+ sSetupPacket.wIndex = 0;
+
+ //
+ // Always 0.
+ //
+ sSetupPacket.wLength = 0;
+
+ //
+ // Now get the full descriptor now that the actual maximum packet size
+ // is known.
+ //
+ USBHCDControlTransfer(0, &sSetupPacket, psHIDInstance->psDevice, 0, 0,
+ psHIDInstance->psDevice->sDeviceDescriptor.bMaxPacketSize0);
+
+ return(0);
+}
+
+//*****************************************************************************
+//
+//! This function is used to retrieve a report from a HID device.
+//!
+//! \param psHIDInstance is the value that was returned from the call to
+//! USBHHIDOpen().
+//! \param ui32Interface is the interface to retrieve the report from.
+//! \param pui8Data is the memory buffer to use to store the report.
+//! \param ui32Size is the size in bytes of the buffer pointed to by
+//! \e pui8Buffer.
+//!
+//! This function is used to retrieve a report from a USB pipe. It is usually
+//! called when the USB HID layer has detected a new data available in a USB
+//! pipe. The USB HID host device code will receive a
+//! \b USB_EVENT_RX_AVAILABLE event when data is available, allowing the
+//! callback function to retrieve the data.
+//!
+//! \return Returns the number of bytes read from report.
+//
+//*****************************************************************************
+uint32_t
+USBHHIDGetReport(tHIDInstance *psHIDInstance, uint32_t ui32Interface,
+ uint8_t *pui8Data, uint32_t ui32Size)
+{
+ //
+ // Read the Data out.
+ //
+ ui32Size = USBHCDPipeReadNonBlocking(psHIDInstance->ui32IntInPipe,
+ pui8Data, ui32Size);
+
+ //
+ // Return the number of bytes read from the interrupt in pipe.
+ //
+ return(ui32Size);
+}
+
+//*****************************************************************************
+//
+//! This function is used to send a report to a HID device.
+//!
+//! \param psHIDInstance is the value that was returned from the call to
+//! USBHHIDOpen().
+//! \param ui32Interface is the interface to send the report to.
+//! \param pui8Data is the memory buffer to use to store the report.
+//! \param ui32Size is the size in bytes of the buffer pointed to by
+//! \e pui8Buffer.
+//!
+//! This function is used to send a report to a USB HID device. It can be
+//! only be called from outside the callback context as this function will not
+//! return from the call until the data has been sent successfully.
+//!
+//! \return Returns the number of bytes sent to the device.
+//
+//*****************************************************************************
+uint32_t
+USBHHIDSetReport(tHIDInstance *psHIDInstance, uint32_t ui32Interface,
+ uint8_t *pui8Data, uint32_t ui32Size)
+{
+ tUSBRequest sSetupPacket;
+
+ //
+ // This is a class specific OUT request.
+ //
+ sSetupPacket.bmRequestType = USB_RTYPE_DIR_OUT | USB_RTYPE_CLASS |
+ USB_RTYPE_INTERFACE;
+
+ //
+ // Request a Report Descriptor.
+ //
+ sSetupPacket.bRequest = USBREQ_SET_REPORT;
+ sSetupPacket.wValue = USB_HID_REPORT_OUTPUT << 8;
+
+ //
+ // Index is always 0 for device requests.
+ //
+ sSetupPacket.wIndex = (uint16_t)ui32Interface;
+
+ //
+ // Always 0.
+ //
+ sSetupPacket.wLength = ui32Size;
+
+ //
+ // Now get the full descriptor now that the actual maximum packet size
+ // is known.
+ //
+ USBHCDControlTransfer(0, &sSetupPacket, psHIDInstance->psDevice,
+ pui8Data, ui32Size,
+ psHIDInstance->psDevice->sDeviceDescriptor.bMaxPacketSize0);
+
+ return(ui32Size);
+}
+
+//*****************************************************************************
+//
+//! This function forwards an LPM request for a device to enter L1 sleep state.
+//!
+//! \param psHIDInstance is the HID instance that was returned from the call
+//! to USBHHIDOpen().
+//!
+//! This function forwards a request from a HID device class to the host
+//! controller to request that a device enter the LPM L1 sleep state. The
+//! caller must check the return value to see if the request can be
+//! attempted at this time. If another LPM transaction is busy on this or
+//! another device, then this function returns \b USBHCD_LPM_PENDING. If
+//! the LPM request was scheduled to be sent the function returns
+//! \b USBHCD_LPM_AVAIL. The caller should check the USBHCDLPMStatus()
+//! function to determine if the request completed successfully or if there
+//! was an error.
+//!
+//! \return This function returns the following values:
+//! - USBHCD_LPM_AVAIL - The transition to L1 state is scheduled to be sent.
+//! - USBHCD_LPM_PENDING - There is already an LPM request pending.
+//
+//*****************************************************************************
+uint32_t
+USBHHIDLPMSleep(tHIDInstance *psHIDInstance)
+{
+ //
+ // Forward the request to the control endpoint of the device.
+ //
+ return(USBHCDLPMSleep(psHIDInstance->psDevice));
+}
+
+//*****************************************************************************
+//
+//! This function returns the current status of an LPM request.
+//!
+//! \param psHIDInstance is the HID instance that was returned from the call
+//! to USBHHIDOpen().
+//!
+//! This function returns the current status of LPM requests for a given
+//! device. This is called to determine if a previous request completed
+//! successfully or if there was an error.
+//!
+//! \return This function returns the following values:
+//! - \b USBHCD_LPM_AVAIL - There are no pending LPM requests on this specific
+//! device or the last request completed successfully.
+//! - \b USBHCD_LPM_ERROR - The last LPM request for this device did not
+//! complete successfully.
+//! - \b USBHCD_LPM_PENDING - The last LPM request has not completed.
+//
+//*****************************************************************************
+uint32_t
+USBHHIDLPMStatus(tHIDInstance *psHIDInstance)
+{
+ //
+ // Call the host controller function to get the current LPM status.
+ //
+ return(USBHCDLPMStatus(psHIDInstance->psDevice));
+}
+
+//*****************************************************************************
+//
+//! @}
+//
+//*****************************************************************************