/*************************************************************************** * This file is part of Qthid. * * Copyright (C) 2010 Howard Long, G6LVB * CopyRight (C) 2011 Alexandru Csete, OZ9AEC * Mario Lorenz, DL5MLO * * Qthid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Qthid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Qthid. If not, see . * ***************************************************************************/ #include "fcdhid.h" /** \brief Open FCD device. * \return Pointer to the FCD HID device or NULL if none found * * This function looks for FCD devices connected to the computer and * opens the first one found. */ hid_device *fcdOpen(uint16_t usVID, uint16_t usPID, int whichdongle) { struct hid_device_info *phdi=NULL; hid_device *phd=NULL; char *pszPath=NULL; phdi = hid_enumerate(usVID, usPID); int which = whichdongle; while (phdi && which) { phdi=phdi->next; which--; } if (phdi==NULL) { return NULL; // No FCD device found } pszPath=strdup(phdi->path); if (pszPath==NULL) { return NULL; } hid_free_enumeration(phdi); phdi=NULL; if ((phd=hid_open_path(pszPath)) == NULL) { free(pszPath); pszPath=NULL; return NULL; } free(pszPath); pszPath=NULL; return phd; } /** \brief Close FCD HID device. */ void fcdClose(hid_device *phd) { hid_close(phd); } /** \brief Get FCD mode. * \return The current FCD mode. * \sa FCD_MODE_ENUM */ FCD_MODE_ENUM fcdGetMode(hid_device *phd) { //hid_device *phd=NULL; unsigned char aucBufIn[65]; unsigned char aucBufOut[65]; FCD_MODE_ENUM fcd_mode = FCD_MODE_NONE; /* phd = fcdOpen(); if (phd == NULL) { return FCD_MODE_DEAD; }*/ /* Send a BL Query Command */ aucBufOut[0] = 0; // Report ID, ignored aucBufOut[1] = FCD_CMD_BL_QUERY; hid_write(phd, aucBufOut, 65); memset(aucBufIn, 0xCC, 65); // Clear out the response buffer hid_read(phd, aucBufIn, 65); /* fcdClose(phd); phd = NULL;*/ /* first check status bytes then check which mode */ if (aucBufIn[0]==FCD_CMD_BL_QUERY && aucBufIn[1]==1) { /* In bootloader mode we have the string "FCDBL" starting at acBufIn[2] **/ if (strncmp((char *)(aucBufIn+2), "FCDBL", 5) == 0) { fcd_mode = FCD_MODE_BL; } /* In application mode we have "FCDAPP_18.06" where the number is the FW version */ else if (strncmp((char *)(aucBufIn+2), "FCDAPP", 6) == 0) { fcd_mode = FCD_MODE_APP; } /* either no FCD or firmware less than 18f */ else { fcd_mode = FCD_MODE_NONE; } } return fcd_mode; } /** \brief Get FCD firmware version as string. * \param str The returned vesion number as a 0 terminated string (must be pre-allocated) * \return The current FCD mode. * \sa FCD_MODE_ENUM */ FCD_MODE_ENUM fcdGetFwVerStr(hid_device *phd, char *str) { //hid_device *phd=NULL; unsigned char aucBufIn[65]; unsigned char aucBufOut[65]; FCD_MODE_ENUM fcd_mode = FCD_MODE_NONE; /* phd = fcdOpen(); if (phd == NULL) { return FCD_MODE_NONE; }*/ /* Send a BL Query Command */ aucBufOut[0] = 0; // Report ID, ignored aucBufOut[1] = FCD_CMD_BL_QUERY; hid_write(phd, aucBufOut, 65); memset(aucBufIn, 0xCC, 65); // Clear out the response buffer hid_read(phd, aucBufIn, 65); /* fcdClose(phd); phd = NULL;*/ /* first check status bytes then check which mode */ if (aucBufIn[0]==FCD_CMD_BL_QUERY && aucBufIn[1]==1) { /* In bootloader mode we have the string "FCDBL" starting at acBufIn[2] **/ if (strncmp((char *)(aucBufIn+2), "FCDBL", 5) == 0) { fcd_mode = FCD_MODE_BL; } /* In application mode we have "FCDAPP_18.06" where the number is the FW version */ else if (strncmp((char *)(aucBufIn+2), "FCDAPP", 6) == 0) { strncpy(str, (char *)(aucBufIn+9), 5); str[5] = 0; fcd_mode = FCD_MODE_APP; } /* either no FCD or firmware less than 18f */ else { fcd_mode = FCD_MODE_NONE; } } return fcd_mode; } /** \brief Get hardware and firmware dependent FCD capabilities. * \param fcd_caps Pointer to an FCD_CAPS_STRUCT * \return The current FCD mode. * * This function queries the FCD and extracts the hardware and firmware dependent * capabilities. Currently these capabilities are: * - Bias T (available since S/N TBD) * - Cellular block (the certified version of the FCD) * When the FCD is in application mode, the string returned by the query command is * (starting at index 2): * FCDAPP 18.08 Brd 1.0 No blk * 1.0 means no bias tee, 1.1 means there is a bias tee * 'No blk' means it is not cellular blocked. * * Ref: http://uk.groups.yahoo.com/group/FCDevelopment/message/303 */ FCD_MODE_ENUM fcdGetCaps(hid_device *phd, FCD_CAPS_STRUCT *fcd_caps) { //hid_device *phd=NULL; unsigned char aucBufIn[65]; unsigned char aucBufOut[65]; FCD_MODE_ENUM fcd_mode = FCD_MODE_NONE; /* clear output buffer */ fcd_caps->hasBiasT = 0; fcd_caps->hasCellBlock = 0; /* phd = fcdOpen(); if (phd == NULL) { return FCD_MODE_NONE; }*/ /* Send a BL Query Command */ aucBufOut[0] = 0; // Report ID, ignored aucBufOut[1] = FCD_CMD_BL_QUERY; hid_write(phd, aucBufOut, 65); memset(aucBufIn, 0xCC, 65); // Clear out the response buffer hid_read(phd, aucBufIn, 65); /* fcdClose(phd); phd = NULL;*/ /* first check status bytes then check which mode */ if (aucBufIn[0]==FCD_CMD_BL_QUERY && aucBufIn[1]==1) { /* In bootloader mode we have the string "FCDBL" starting at acBufIn[2] **/ if (strncmp((char *)(aucBufIn+2), "FCDBL", 5) == 0) { fcd_mode = FCD_MODE_BL; } /* In application mode we have "FCDAPP 18.08 Brd 1.0 No blk" (see API doc) */ else if (strncmp((char *)(aucBufIn+2), "FCDAPP", 6) == 0) { /* Bias T */ fcd_caps->hasBiasT = (aucBufIn[21] == '1') ? 1 : 0; /* cellular block */ if (strncmp((char *)(aucBufIn+23), "No blk", 6) == 0) { fcd_caps->hasCellBlock = 0; } else { fcd_caps->hasCellBlock = 1; } fcd_mode = FCD_MODE_APP; } /* either no FCD or firmware less than 18f */ else { fcd_mode = FCD_MODE_NONE; } } return fcd_mode; } /** \brief Get hardware and firmware dependent FCD capabilities as string. * \param caps_str Pointer to a pre-allocated string buffer where the info will be copied. * \return The current FCD mode. * * This function queries the FCD and copies the returned string into the caps_str parameter. * THe return buffer must be at least 28 characters. * When the FCD is in application mode, the string returned by the query command is * (starting at index 2): * FCDAPP 18.08 Brd 1.0 No blk * 1.0 means no bias tee, 1.1 means there is a bias tee * 'No blk' means it is not cellular blocked. * * Ref: http://uk.groups.yahoo.com/group/FCDevelopment/message/303 */ FCD_MODE_ENUM fcdGetCapsStr(hid_device *phd, char *caps_str) { //hid_device *phd=NULL; unsigned char aucBufIn[65]; unsigned char aucBufOut[65]; FCD_MODE_ENUM fcd_mode = FCD_MODE_NONE; /* phd = fcdOpen(); if (phd == NULL) { return FCD_MODE_NONE; }*/ /* Send a BL Query Command */ aucBufOut[0] = 0; // Report ID, ignored aucBufOut[1] = FCD_CMD_BL_QUERY; hid_write(phd, aucBufOut, 65); memset(aucBufIn, 0xCC, 65); // Clear out the response buffer hid_read(phd, aucBufIn, 65); /* fcdClose(phd); phd = NULL;*/ /* first check status bytes then check which mode */ if (aucBufIn[0]==FCD_CMD_BL_QUERY && aucBufIn[1]==1) { /* In bootloader mode we have the string "FCDBL" starting at acBufIn[2] **/ if (strncmp((char *)(aucBufIn+2), "FCDBL", 5) == 0) { fcd_mode = FCD_MODE_BL; } /* In application mode we have "FCDAPP 18.08 Brd 1.0 No blk" (see API doc) */ else if (strncmp((char *)(aucBufIn+2), "FCDAPP", 6) == 0) { strncpy(caps_str, (char *)(aucBufIn+2), 27); caps_str[27] = 0; fcd_mode = FCD_MODE_APP; } /* either no FCD or firmware less than 18f */ else { fcd_mode = FCD_MODE_NONE; } } return fcd_mode; } /** \brief Reset FCD to bootloader mode. * \return FCD_MODE_NONE * * This function is used to switch the FCD into bootloader mode in which * various firmware operations can be performed. */ FCD_MODE_ENUM fcdAppReset(hid_device *phd) { //hid_device *phd=NULL; //unsigned char aucBufIn[65]; unsigned char aucBufOut[65]; /* phd = fcdOpen(); if (phd == NULL) { return FCD_MODE_NONE; }*/ // Send an App reset command aucBufOut[0] = 0; // Report ID, ignored aucBufOut[1] = FCD_CMD_APP_RESET; hid_write(phd, aucBufOut, 65); /** FIXME: hid_read() will occasionally hang due to a pthread_cond_wait() never returning. It seems that the read_callback() in hid-libusb.c will never receive any data during the reconfiguration. Since the same logic works in the native windows application, it could be a libusb thing. Anyhow, since the value returned by this function is not used, we may as well just skip the hid_read() and return FME_NONE. Correct switch from APP to BL mode can be observed in /var/log/messages (linux) (when in bootloader mode the device version includes 'BL') */ /* memset(aucBufIn,0xCC,65); // Clear out the response buffer hid_read(phd,aucBufIn,65); if (aucBufIn[0]==FCDCMDAPPRESET && aucBufIn[1]==1) { FCDClose(phd); phd=NULL; return FME_APP; } FCDClose(phd); phd=NULL; return FME_BL; */ /* fcdClose(phd); phd = NULL;*/ return FCD_MODE_NONE; } /** \brief Set FCD frequency with kHz resolution. * \param nFreq The new frequency in kHz. * \return The FCD mode. * * This function sets the frequency of the FCD with 1 kHz resolution. The parameter * nFreq must already contain any necessary frequency correction. * * \sa fcdAppSetFreq */ FCD_MODE_ENUM fcdAppSetFreqkHz(hid_device *phd, int nFreq) { //hid_device *phd=NULL; unsigned char aucBufIn[65]; unsigned char aucBufOut[65]; /* phd = fcdOpen(); if (phd == NULL) { return FCD_MODE_NONE; }*/ // Send an App reset command aucBufOut[0] = 0; // Report ID, ignored aucBufOut[1] = FCD_CMD_APP_SET_FREQ_KHZ; aucBufOut[2] = (unsigned char)nFreq; aucBufOut[3] = (unsigned char)(nFreq>>8); aucBufOut[4] = (unsigned char)(nFreq>>16); hid_write(phd, aucBufOut, 65); memset(aucBufIn, 0xCC, 65); // Clear out the response buffer hid_read(phd, aucBufIn, 65); if (aucBufIn[0]==FCD_CMD_APP_SET_FREQ_KHZ && aucBufIn[1]==1) { /* fcdClose(phd); phd = NULL;*/ return FCD_MODE_APP; } /* fcdClose(phd); phd = NULL;*/ return FCD_MODE_BL; } /** \brief Set FCD frequency with Hz resolution. * \param nFreq The new frequency in Hz. * \return The FCD mode. * * This function sets the frequency of the FCD with 1 Hz resolution. The parameter * nFreq must already contain any necessary frequency correction. * * \sa fcdAppSetFreq */ FCD_MODE_ENUM fcdAppSetFreq(hid_device *phd, int nFreq) { //hid_device *phd=NULL; unsigned char aucBufIn[65]; unsigned char aucBufOut[65]; /* phd = fcdOpen(); if (phd == NULL) { return FCD_MODE_NONE; }*/ // Send an App reset command aucBufOut[0] = 0; // Report ID, ignored aucBufOut[1] = FCD_CMD_APP_SET_FREQ_HZ; aucBufOut[2] = (unsigned char)nFreq; aucBufOut[3] = (unsigned char)(nFreq>>8); aucBufOut[4] = (unsigned char)(nFreq>>16); aucBufOut[5] = (unsigned char)(nFreq>>24); hid_write(phd, aucBufOut, 65); memset(aucBufIn, 0xCC, 65); // Clear out the response buffer hid_read(phd, aucBufIn, 65); if (aucBufIn[0]==FCD_CMD_APP_SET_FREQ_HZ && aucBufIn[1]==1) { /* fcdClose(phd); phd = NULL;*/ return FCD_MODE_APP; } /* fcdClose(phd); phd = NULL;*/ return FCD_MODE_BL; } /** \brief Reset FCD to application mode. * \return FCD_MODE_NONE * * This function is used to switch the FCD from bootloader mode * into application mode. */ FCD_MODE_ENUM fcdBlReset(hid_device *phd) { //hid_device *phd=NULL; // unsigned char aucBufIn[65]; unsigned char aucBufOut[65]; /* phd = fcdOpen(); if (phd == NULL) { return FCD_MODE_NONE; }*/ // Send an BL reset command aucBufOut[0] = 0; // Report ID, ignored aucBufOut[1] = FCD_CMD_BL_RESET; hid_write(phd, aucBufOut, 65); /** FIXME: hid_read() will hang due to a pthread_cond_wait() never returning. It seems that the read_callback() in hid-libusb.c will never receive any data during the reconfiguration. Since the same logic works in the native windows application, it could be a libusb thing. Anyhow, since the value returned by this function is not used, we may as well jsut skip the hid_read() and return FME_NONE. Correct switch from BL to APP mode can be observed in /var/log/messages (linux) (when in bootloader mode the device version includes 'BL') */ /* memset(aucBufIn,0xCC,65); // Clear out the response buffer hid_read(phd,aucBufIn,65); if (aucBufIn[0]==FCDCMDBLRESET && aucBufIn[1]==1) { FCDClose(phd); phd=NULL; return FME_BL; } FCDClose(phd); phd=NULL; return FME_APP; */ /* fcdClose(phd); phd = NULL;*/ return FCD_MODE_NONE; } /** \brief Erase firmware from FCD. * \return The FCD mode * * This function deletes the firmware from the FCD. This is required * before writing new firmware into the FCD. * * \sa fcdBlWriteFirmware */ FCD_MODE_ENUM fcdBlErase(hid_device *phd) { //hid_device *phd=NULL; unsigned char aucBufIn[65]; unsigned char aucBufOut[65]; /* phd = fcdOpen(); if (phd == NULL) { return FCD_MODE_NONE; }*/ // Send an App reset command aucBufOut[0] = 0; // Report ID, ignored aucBufOut[1] = FCD_CMD_BL_ERASE; hid_write(phd, aucBufOut, 65); memset(aucBufIn, 0xCC, 65); // Clear out the response buffer hid_read(phd, aucBufIn, 65); if (aucBufIn[0]==FCD_CMD_BL_ERASE && aucBufIn[1]==1) { /* fcdClose(phd); phd = NULL;*/ return FCD_MODE_BL; } /* fcdClose(phd); phd = NULL;*/ return FCD_MODE_APP; } /** \brief Write new firmware into the FCD. * \param pc Pointer to the new firmware data * \param n64size The number of bytes in the data * \return The FCD mode * * This function is used to upload new firmware into the FCD flash. * * \sa fcdBlErase */ FCD_MODE_ENUM fcdBlWriteFirmware(hid_device *phd, char *pc, int64_t n64Size) { //hid_device *phd=NULL; unsigned char aucBufIn[65]; unsigned char aucBufOut[65]; uint32_t u32AddrStart; uint32_t u32AddrEnd; uint32_t u32Addr; /* phd = fcdOpen(); if (phd==NULL) { return FCD_MODE_NONE; }*/ // Get the valid flash address range aucBufOut[0] = 0; // Report ID, ignored aucBufOut[1] = FCD_CMD_BL_GET_BYTE_ADDR_RANGE; hid_write(phd, aucBufOut, 65); memset(aucBufIn, 0xCC, 65); // Clear out the response buffer hid_read(phd, aucBufIn, 65); if (aucBufIn[0]!=FCD_CMD_BL_GET_BYTE_ADDR_RANGE || aucBufIn[1]!=1) { /* fcdClose(phd); phd = NULL;*/ return FCD_MODE_APP; } u32AddrStart= aucBufIn[2]+ (((uint32_t)aucBufIn[3])<<8)+ (((uint32_t)aucBufIn[4])<<16)+ (((uint32_t)aucBufIn[5])<<24); u32AddrEnd= aucBufIn[6]+ (((uint32_t)aucBufIn[7])<<8)+ (((uint32_t)aucBufIn[8])<<16)+ (((uint32_t)aucBufIn[9])<<24); // Set start address for flash aucBufOut[0] = 0; // Report ID, ignored aucBufOut[1] = FCD_CMD_BL_SET_BYTE_ADDR; aucBufOut[2] = ((unsigned char)u32AddrStart); aucBufOut[3] = ((unsigned char)(u32AddrStart>>8)); aucBufOut[4] = ((unsigned char)(u32AddrStart>>16)); aucBufOut[5] = ((unsigned char)(u32AddrStart>>24)); hid_write(phd, aucBufOut, 65); memset(aucBufIn, 0xCC, 65); // Clear out the response buffer hid_read(phd, aucBufIn, 65); if (aucBufIn[0]!=FCD_CMD_BL_SET_BYTE_ADDR || aucBufIn[1]!=1) { /* fcdClose(phd); phd = NULL;*/ return FCD_MODE_APP; } // Write blocks aucBufOut[0] = 0; // Report ID, ignored aucBufOut[1] = FCD_CMD_BL_WRITE_FLASH_BLOCK; for (u32Addr=u32AddrStart; u32Addr+47>8)); aucBufOut[4] = ((unsigned char)(u32AddrStart>>16)); aucBufOut[5] = ((unsigned char)(u32AddrStart>>24)); hid_write(phd, aucBufOut, 65); memset(aucBufIn, 0xCC, 65); // Clear out the response buffer hid_read(phd, aucBufIn, 65); if (aucBufIn[0]!=FCD_CMD_BL_SET_BYTE_ADDR || aucBufIn[1]!=1) { /* fcdClose(phd); phd = NULL;*/ return FCD_MODE_APP; } // Read blocks aucBufOut[0] = 0; // Report ID, ignored aucBufOut[1] = FCD_CMD_BL_READ_FLASH_BLOCK; for (u32Addr=u32AddrStart; u32Addr+47