/***************************************************************************
* 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 .
*
***************************************************************************/
#define FCD
#include
#include
#include "hidapi.h"
#include "fcd.h"
#include "fcdhidcmd.h"
#include
#include
#define TRUE true
#define FALSE false
typedef bool BOOL;
#define FCDPP
const unsigned short _usVID=0x04D8; /*!< USB vendor ID. */
#ifdef FCDPP
const unsigned short _usPID=0xFB31; /*!< USB product ID. */
#else
const unsigned short _usPID=0xFB56; /*!< USB product ID. */
#endif
int whichdongle=0;
/** \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.
*/
static hid_device *fcdOpen(void)
{
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. */
static void fcdClose(hid_device *phd)
{
hid_close(phd);
}
/** \brief Get FCD mode.
* \return The current FCD mode.
* \sa FCD_MODE_ENUM
*/
EXTERN FCD_API_EXPORT FCD_API_CALL FCD_MODE_ENUM fcdGetMode(void)
{
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
*/
EXTERN FCD_API_EXPORT FCD_API_CALL FCD_MODE_ENUM fcdGetFwVerStr(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
*/
EXTERN FCD_API_EXPORT FCD_API_CALL FCD_MODE_ENUM fcdGetCaps(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
*/
EXTERN FCD_API_EXPORT FCD_API_CALL FCD_MODE_ENUM fcdGetCapsStr(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.
*/
EXTERN FCD_API_EXPORT FCD_API_CALL FCD_MODE_ENUM fcdAppReset(void)
{
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
*/
EXTERN FCD_API_EXPORT FCD_API_CALL FCD_MODE_ENUM fcdAppSetFreqkHz(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
*/
EXTERN FCD_API_EXPORT FCD_API_CALL FCD_MODE_ENUM fcdAppSetFreq(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.
*/
EXTERN FCD_API_EXPORT FCD_API_CALL FCD_MODE_ENUM fcdBlReset(void)
{
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
*/
EXTERN FCD_API_EXPORT FCD_API_CALL FCD_MODE_ENUM fcdBlErase(void)
{
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
*/
EXTERN FCD_API_EXPORT FCD_API_CALL FCD_MODE_ENUM fcdBlWriteFirmware(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;
BOOL bFinished=FALSE;
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