/*
 *****************************************************************************
 *                                                                           *
 *                 IMPINJ CONFIDENTIAL AND PROPRIETARY                       *
 *                                                                           *
 * This source code is the sole property of Impinj, Inc.  Reproduction or    *
 * utilization of this source code in whole or in part is forbidden without  *
 * the prior written consent of Impinj, Inc.                                 *
 *                                                                           *
 * (c) Copyright Impinj, Inc. 2009. All rights reserved.                     *
 *                                                                           *
 *****************************************************************************
 */

/*
 *****************************************************************************
 *
 * $Id: oemconfig.c 66452 2010-12-22 20:12:49Z dshaheen $
 * 
 * Description:
 *     This is a simple sample application for reading the OEM configuration
 *     memory.  It reads from the beginning of the OEM memory space up through
 *     the HWOPTIONS3 entry, into an OEMCONFIG_AREA data structure.  It then 
 *     dumps a few key parameters of the OEMCONFIG_AREA to the console for 
 *     inspection.  
 *
 *****************************************************************************
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "rfid_types.h"
#include "rfid_library.h"
#include "byte_swap.h"
#include "oemcfg.h"

/* NOTE - in this sample code, pay close attention to the code that           */
/* manipulates multi-byte (i.e., 16- and 32-bit values) that are read from the*/
/* MAC in that they perform a conversion from MAC to host before they are     */
/* used.  If these values were going to be written back to the MAC, you must  */
/* ensure that they are in the MAC's native format (i.e., little endian).     */

#define OEMCFG_USBD_32BIT_LEN           40
#define OEMCFG_USBD_16BIT_LEN           OEMCFG_USBD_32BIT_LEN * 2
#define OEMCFGADDR_PBA_NUM_32BIT_LEN    4

typedef struct
{
    INT8U   byteLength;
    INT8U   usbDescriptorType;
    INT16U  name[OEMCFG_USBD_16BIT_LEN - 1];
} USB_STRING_DESCRIPTOR;


#define MANUFACTURE_YEAR(x)         ((x) & 0x0000FFFF)
#define MANUFACTURE_WW(x)           (((x) >> 16) & 0x000000FF)
#define MANUFACTURE_MONTH(x)        ((x) & 0x0000000F)
#define MANUFACTURE_DAY(x)          (((x) >> 4) & 0x0000000F)
#define MANUFACTURE_HOUR(x)         ((((x) >> 8) & 0x00000FFF) / 60)
#define MANUFACTURE_MINUTE(x)       ((((x) >> 8) & 0x00000FFF) % 60)
#define MANUFACTURE_SECOND(x)       (((x) >> 20) & 0x000000FF)
#define CALC_REFERENCE_VOLTAGE(x)   ((float) ((x) >> 16) +                     \
                                     (((float) ((x) & 0x0000FFFF)) / 0xFFFF))
#define CALC_FWDPWR_COEFFICIENT(x)  ((float) ((x) >> 24) +                     \
                                     (((float) ((x) & 0x00FFFFFF)) / 0xFFFFFF))
#define HW_OPT_FWD_POWER_DETECT(x)  ((x) & 0x0000000F)
#define HW_OPT_REV_POWER_DETECT(x)  (((x) >> 4) & 0x0000000F)
#define HW_OPT_DRM_FILTER_ENABLE(x) (((x) >> 8) & 0x0000000F)
#define HW_OPT_PA_TEMP_SENSOR(x)    (((x) >> 12) & 0x0000000F)
#define HW_OPT_AMB_TEMP_SENSOR(x)   (((x) >> 16) & 0x0000000F)
#define HW_OPT_XCVR_TEMP_SENSOR(x)  (((x) >> 20) & 0x0000000F)
#define HW_OPT_ANT_SENSE_DETECT(x)  (((x) >> 24) & 0x0000000F)

/* This function converts the OEM config structure from MAC to host native    */
/* format.  This function is necessary to ensure that the OEM config area is  */
/* properly interpreted when read.                                            */
static void OemConfigMacToHost(
    OEMCFG_AREA_MAP* pConfig
    );
static void Int32ArrayMacToHost(
    INT32U*         pBegin,
    INT32U          numberInt32
    );
static void Int16ArrayMacToHost(
    INT16U*         pBegin,
    INT32U          numberInt16
    );

/* Note - this function converts the USB string descriptor to an ASCII string.*/
/* It is really brain dead - it simply takes the low-order bytes out of the   */
/* 16-bit integer.  One would think that wcstombs would work or that one      */
/* could use the ls format specifier to printf to print it out, but alas      */
/* neither seem to work on our distro of Linux.                               */
static void ConvertUsbString(
    char*           destination,
    const INT16U*   source,
    INT32U          length
    );

int main(
    int     argc,
    char**  argv
    )
{
    RFID_STATUS              status;
    RFID_RADIO_ENUM*         pEnum;
    RFID_RADIO_HANDLE        handle;
    OEMCFG_AREA_MAP          oemConfig;
    char                     tempString[OEMCFG_USBD_32BIT_LEN * 4];
    INT32U                   numToRead = ((INT32U)&oemConfig.hw_options4 - (INT32U)&oemConfig) / sizeof(INT32U) ;
    char*                    regionString;
    USB_STRING_DESCRIPTOR*   pManufacturerName;
    USB_STRING_DESCRIPTOR*   pProductName;
    USB_STRING_DESCRIPTOR*   pSerialNumber;


    RFID_UNREFERENCED_LOCAL(argc);
    RFID_UNREFERENCED_LOCAL(argv);

    /* Initialize the RFID library                                         */
    status = RFID_Startup(NULL, 0);
    if (RFID_STATUS_OK != status)
    {
        fprintf(stderr, "ERROR: RFID_Startup returned %d\n", status);
        goto EXIT;
    }

    /* Create an initial structure for enumerating the radios.  We'll adjust  */
    /* it once we know how big to make it.                                    */
    pEnum = (RFID_RADIO_ENUM *) malloc(sizeof(RFID_RADIO_ENUM));
    if (NULL == pEnum)
    {
        fprintf(stderr, "ERROR: Failed to allocate memory\n");
        goto SHUTDOWN_LIBRARY;
    }
    pEnum->length       =
        pEnum->totalLength  = sizeof(RFID_RADIO_ENUM);

    /* Enumerate the radios                                                   */
    while (RFID_ERROR_BUFFER_TOO_SMALL == 
        (status = RFID_RetrieveAttachedRadiosList(pEnum, 0)))
    {
        RFID_RADIO_ENUM* pNewEnum = 
            (RFID_RADIO_ENUM *) realloc(pEnum, pEnum->totalLength);
        if (NULL == pNewEnum)
        {
            fprintf(stderr, "ERROR: Failed to allocate memory\n");
            goto CLEANUP_ENUM;
        }
        pEnum = pNewEnum;
    }
    if (RFID_STATUS_OK != status)
    {
        fprintf(
            stderr,
            "ERROR: RFID_RetrieveAttachedRadiosList returned %d\n",
            status);
        goto CLEANUP_ENUM;
    }

    /* Open up the first radio - if more than one radio is attached, so be it */
    if (!pEnum->countRadios)
    {
        fprintf(stderr, "ERROR: No radios attached to the system\n");
        goto CLEANUP_ENUM;
    }
    status = RFID_RadioOpen(pEnum->ppRadioInfo[0]->cookie, &handle, 0);
    if (RFID_STATUS_OK != status)
    {
        fprintf(stderr, "ERROR: RFID_RadioOpen returned %d\n", status);
        goto CLEANUP_ENUM;
    }

    /* Clear out the oemConfig structure, since we only load/read a fraction of it */
    memset(&oemConfig, 0, sizeof(oemConfig));

    /* Read the OEM configuration area and then convert it to the host-native */
    /* format.  Note, if we were going to write this back to the MAC, then    */
    /* we would have to convert it to MAC-native format before writing it.    */
    status = 
        RFID_MacReadOemData(
        handle,
        0,
        &numToRead,
        (INT32U *) &oemConfig);
    if (RFID_STATUS_OK != status)
    {
        fprintf(stderr, "ERROR: RFID_MacReadOemData returned %d\n", status);
        goto CLOSE_RADIO;
    }
    OemConfigMacToHost(&oemConfig);

    printf(
        "OEM Configuration Area (Partial) Contents:\n"
        "------------------------------------------\n");
    printf(
        "Manufacture Date:\t\tWW=%d,DATE=%d/%d/%d %d:%.2d:%.2d\n",
        MANUFACTURE_YEAR(oemConfig.ww_year),
        MANUFACTURE_WW(oemConfig.ww_year),
        MANUFACTURE_MONTH(oemConfig.mon_day_min_sec),
        MANUFACTURE_DAY(oemConfig.mon_day_min_sec),
        MANUFACTURE_HOUR(oemConfig.mon_day_min_sec),
        MANUFACTURE_MINUTE(oemConfig.mon_day_min_sec),
        MANUFACTURE_SECOND(oemConfig.mon_day_min_sec));
    printf(
        "ADC Reference Voltage (Deprecated):\t\t%f volts\n",
        CALC_REFERENCE_VOLTAGE(oemConfig.reserved_090));
    printf(
        "RF Forward Power Coefficient (2nd order coefficent):\t%f\n",
        CALC_FWDPWR_COEFFICIENT(oemConfig.rfcal_fwdpwr_a2));
    printf(
        "RF Forward Power Coefficient (1st order coefficent):\t%f\n",
        CALC_FWDPWR_COEFFICIENT(oemConfig.rfcal_fwdpwr_a1));
    printf(
        "RF Forward Power Coefficient (0th order coefficent):\t%f\n",
        CALC_FWDPWR_COEFFICIENT(oemConfig.rfcal_fwdpwr_a0));
    printf("USB Vendor ID:\t\t\t0x%.8x\n", oemConfig.usb_vid);
    printf("USB Product ID:\t\t\t0x%.8x\n", oemConfig.usb_pid);

    pManufacturerName = (USB_STRING_DESCRIPTOR*)oemConfig.mfg_name;
    ConvertUsbString(
        tempString, 
        pManufacturerName->name,
        (pManufacturerName->byteLength - 2) / sizeof(INT16U));
    printf(
        "Manufacturer:\t\t\t%.*s\n",
        (pManufacturerName->byteLength - 2) / sizeof(INT16U),
        tempString);

    pProductName = (USB_STRING_DESCRIPTOR*)oemConfig.prod_name;
    ConvertUsbString(
        tempString, 
        pProductName->name,
        (pProductName->byteLength - 2) / sizeof(INT16U));
    printf(
        "Product Name:\t\t\t%.*s\n",
        (pProductName->byteLength - 2) / sizeof(INT16U),
        tempString);

    pSerialNumber = (USB_STRING_DESCRIPTOR*)oemConfig.serial_num;
    ConvertUsbString(
        tempString, 
        pSerialNumber->name,
        (pSerialNumber->byteLength - 2) / sizeof(INT16U));
    printf(
        "Serial Number:\t\t\t%.*s\n",
        (pSerialNumber->byteLength - 2) / sizeof(INT16U),
        tempString);
    printf("RSSI Threshod:\t\t\t%d dBm\n", oemConfig.rssi_threshold);
    if (0 == oemConfig.regulatory_region)
    {
        regionString = "FCC";
    }
    else if (1 == oemConfig.regulatory_region)
    {
        regionString = "ETSI";
    }
    else if (2 == oemConfig.regulatory_region)
    {
        regionString = "ETSI(LBT)";
    }
    else 
    {
        regionString = "???";
    }
    printf("Regulatory Region:\t\t%s\n",regionString);
    switch (oemConfig.hw_options_format)
    {
        case 0:
            printf(
                "Forward Power Detector:\t\t%s\n",
                HW_OPT_FWD_POWER_DETECT(oemConfig.hw_options0) ?
                "Use" : "Disable");
            printf(
                "Reverse Power Detector:\t\t%s\n",
                HW_OPT_REV_POWER_DETECT(oemConfig.hw_options0) ?
                "Use" : "Disable");
            printf(
                "DRM Filter:\t\t\t%s\n",
                HW_OPT_DRM_FILTER_ENABLE(oemConfig.hw_options0) ?
                "Enable" : "Disable");
            printf(
                "PA Temperature Sensor:\t\t%s\n",
                HW_OPT_PA_TEMP_SENSOR(oemConfig.hw_options0) ?
                "Use" : "Don't Use");
            printf(
                "Ambient Temperature Sensor:\t%s\n",
                HW_OPT_AMB_TEMP_SENSOR(oemConfig.hw_options0) ?
                "Use" : "Don't Use");
            printf(
                "Transceiver Temperature Sensor:\t%s\n",
                HW_OPT_XCVR_TEMP_SENSOR(oemConfig.hw_options0) ?
                "Use" : "Don't Use");
            printf(
                "Antenna-Sense Detection:\t%s\n",
                HW_OPT_ANT_SENSE_DETECT(oemConfig.hw_options0) ?
                "Measure" : "Don't Measure");
            break;
        default:
            break;
    }

    CLOSE_RADIO:
    RFID_RadioClose(handle);

    CLEANUP_ENUM:
    free(pEnum);

    SHUTDOWN_LIBRARY:
    RFID_Shutdown();

    EXIT:
    return 0;
} /* main */

void ConvertUsbString(
    char*           destination,
    const INT16U*   source,
    INT32U          length
    )
{
    /* Cheap way to do Unicode to ASCII - this assumes that the Unicode string*/
    /* really just represents an ASCII string and we are simply removing the  */
    /* upper nibble (which is 0x00).                                          */
    while (length && (*source != 0))
    {
        length--;
        *destination++ = (char) *source++;         
    }

} /* ConvertUsbString */

void OemConfigMacToHost(
    OEMCFG_AREA_MAP* pConfig
    )
{

    USB_STRING_DESCRIPTOR*   pManufacturerName;
    USB_STRING_DESCRIPTOR*   pProductName;
    USB_STRING_DESCRIPTOR*   pSerialNumber;

    /* Convert the 32-bit integers up to the manufacturer name first and then */
    /* convert the 32-bit integers after the serial number through the end.   */
    Int32ArrayMacToHost(
        (INT32U *) pConfig,
        ((INT32U *) &pConfig->mfg_name) - ((INT32U *) pConfig));
    Int32ArrayMacToHost(
        &pConfig->ant_avail,
        ((INT32U *) (pConfig + 1)) - &pConfig->ant_avail);

    /* Convert the USB Unicode strings to host format.                        */
    pManufacturerName = (USB_STRING_DESCRIPTOR*)pConfig->mfg_name;
    pProductName      = (USB_STRING_DESCRIPTOR*)pConfig->prod_name;
    pSerialNumber     = (USB_STRING_DESCRIPTOR*)pConfig->serial_num;
    Int16ArrayMacToHost(
        pManufacturerName->name,
        (pManufacturerName->byteLength - 2) /2);
    Int16ArrayMacToHost(
        pProductName->name,
        (pProductName->byteLength - 2) /2);
    Int16ArrayMacToHost(
        pSerialNumber->name,
        (pSerialNumber->byteLength - 2) /2);
} /* OemConfigMacToHost */

void Int32ArrayMacToHost(
    INT32U*         pBegin,
    INT32U          numberInt32
    )
{
    for ( ; numberInt32; --numberInt32, ++pBegin)
    {
        *pBegin = MacToHost32(*pBegin);
    }
} /* Int32ArrayMacToHost */

void Int16ArrayMacToHost(
    INT16U*         pBegin,
    INT32U          numberInt16
    )
{
    for ( ; numberInt16; --numberInt16, ++pBegin)
    {
        *pBegin = MacToHost16(*pBegin);
    }
} /* Int16ArrayMacToHost */
