// Copyright (c) 2010, Atmel Corporation. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Atmel nor the // names of its contributors may be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <signal.h> #include <string.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/un.h> #include <sys/stat.h> #include <errno.h> #include <linux/netlink.h> #include <cutils/properties.h> #include <hardware_legacy/power.h> #include <stdlib.h> #include <time.h> #include <linux/capability.h> #include <linux/prctl.h> #include <private/android_filesystem_config.h> #include <linux/spi/cpcap.h> #include <linux/hidraw.h> #include "SA_Phys_Linux.h" #include "SHA_CommMarshalling.h" #include "SHA_Status.h" #include "SHA_Comm.h" #include "SHA_TimeUtils.h" #include "Whisper_AccyMain.h" /*================================================================================================== LOCAL CONSTANTS ==================================================================================================*/ static const char xintToChar[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; static const char wakeLockString[] = "ACCYDET"; static const uint8_t hidStatusQuery[] = {0x3f, 0x30, 0x00, 0x00, 0x42, 0x99}; static const uint8_t hidIdQuery[] = {0x3f, 0x12, 0x00, 0x01, 0xf1, 0xe2}; /*================================================================================================== LOCAL MACROS ==================================================================================================*/ #define MAX_TRY_WAKEUP 4 #define MAX_TRY_COMM 2 #define UART_SWITCH_STATE_PATH "/sys/class/switch/whisper/state" #define HID_SWITCH_STATE_PATH "/sys/class/switch/whisper_hid/state" #define DOCK_TYPE_OFFSET 27 #define DOCK_ATTACHED '1' #define DOCK_NOT_ATTACHED '0' #define HID_SUCCESS 1 #define HID_FAILURE 0 #define ID_SUCCESS 1 #define IOCTL_SUCCESS 0 #define MAX_TRY_IOCTL 5 #define HID_STATUS_QUERY_LENGTH 64 #define HID_ID_QUERY_LENGTH 64 #define HID_MAC_QUERY_LENGTH 64 #define HID_STATUS_MSG_LENGTH 64 #define HID_ID_MSG_LENGTH 64 #define HID_MAC_MSG_LENGTH 64 #define LOG_FILE_NAME "/data/whisper/whisperd.log" #define LOG_FILE_PATH "/data/whisper" /*================================================================================================== EXTERNAL VARIABLES ==================================================================================================*/ /*================================================================================================== LOCAL FUNCTION PROTOTYPES ==================================================================================================*/ static void copyResults(int8_t cmdSize, uint8_t *out); static int accyInit(void); static void accySigHandler(signed int signal); static void accyProtDaemon(void *arg); static void readSwitchState(int); static int accySpawnThread(); static void createOutput(uint8_t *inp, char *out, int bytes); static void waitForUevents(); static void doIoctl(int cmd, unsigned int data, char *dev_id, char *dev_prop); /*================================================================================================== LOCAL VARIABLES ==================================================================================================*/ static SHA_CommParameters* mainparms; static sem_t SigAccyProtStart; static int ueventFd; static int wakeLock = 0; static int globalState; static int globalProtocol; static int cpcapFd = -1; static int hidFd = -1; /*================================================================================================== GLOBAL VARIABLES ==================================================================================================*/ FILE *logFp = NULL; int currLogSize = 0; /*================================================================================================== LOCAL FUNCTIONS ==================================================================================================*/ static void readSwitchState(int protocolType) { const int SIZE = 16; int switchFd = -1; char buf[SIZE]; int count; if (protocolType == PROTOCOL_UART) { switchFd = open(UART_SWITCH_STATE_PATH, O_RDONLY, 0); if (switchFd == -1) { DBG_ERROR("Failed opening %s, errno = %s", UART_SWITCH_STATE_PATH, strerror(errno)); return; } } else if (protocolType == PROTOCOL_HID) { switchFd = open(HID_SWITCH_STATE_PATH, O_RDONLY, 0); if (switchFd == -1) { DBG_ERROR("Failed opening %s, errno = %s", HID_SWITCH_STATE_PATH, strerror(errno)); return; } } do { count = read(switchFd, buf, SIZE); } while (count < 0 && errno == EINTR); if (count < 1) { DBG_ERROR("Error reading switch, returned %d", count); close(switchFd); return; } if (buf[0] == DOCK_ATTACHED) { globalState = GLOBAL_STATE_DOCKED; if (protocolType == PROTOCOL_HID) { char hidDevice[] = "/dev/hidraw0"; int i; struct hidraw_devinfo hiddevinfo; int status; globalProtocol = PROTOCOL_HID; DBG_TRACE("HID Dock Attached"); for (i = 0; i < HIDRAW_MAX_DEVICES; i++) { hidFd = open(hidDevice, O_RDWR); if(hidFd < 0) { DBG_ERROR("Failed to open HID Device:%s", hidDevice); break; } else { status = ioctl(hidFd, HIDIOCGRAWINFO, &hiddevinfo); if (status != IOCTL_SUCCESS) { DBG_ERROR("ioctl cmd:HIDIOCGRAWINFO failed for %s", hidDevice); } else { if(hiddevinfo.vendor == 0x22b8 && hiddevinfo.product == 0x0938) { DBG_TRACE("Found HD Dock: %s", hidDevice); break; } else { DBG_ERROR("Device: %s not a HD dock", hidDevice); close(hidDevice); } } // TODO: You cant do this. hidDevice[11]++; } } hidDevice[11] = '0'; } else if (protocolType == PROTOCOL_UART) { globalProtocol = PROTOCOL_UART; } } else if (buf[0] == DOCK_NOT_ATTACHED) { globalState = GLOBAL_STATE_UNDOCKED; } close(switchFd); } static void waitForUevents() { fd_set accySet; char msg[1024]; int nready, status; FD_ZERO(&accySet); FD_SET(ueventFd, &accySet); /* at powerup, we might have missed the uevent. So, read switch */ readSwitchState(PROTOCOL_HID); if (globalState == GLOBAL_STATE_DOCKED) { DBG_TRACE("HID Dock attached at Power up"); sem_post(&SigAccyProtStart); } else { readSwitchState(PROTOCOL_UART); if (globalState == GLOBAL_STATE_DOCKED) { DBG_TRACE("UART Dock attached at Power up"); sem_post(&SigAccyProtStart); } } while(1) { nready = select(ueventFd+1, &accySet, NULL, NULL, NULL); if (nready > 0) { if (FD_ISSET(ueventFd, &accySet)) { status = recv(ueventFd, msg, sizeof(msg), MSG_DONTWAIT); if ((status > 0) && (strcasestr(msg, "whisper_hid")) && (strcasestr(msg, "switch"))) { readSwitchState(PROTOCOL_HID); DBG_TRACE("HID: SEM POST after readSwitchState %d", globalState); sem_post(&SigAccyProtStart); } else if ((status > 0) && (strcasestr(msg, "whisper")) && (strcasestr(msg, "switch"))) { readSwitchState(PROTOCOL_UART); DBG_TRACE("UART: SEM POST after readSwitchState %d", globalState); sem_post(&SigAccyProtStart); } } } else { DBG_ERROR("Select errored out. nready = %d, errno = %s", nready, strerror(errno)); } } } static int accySpawnThread() { pthread_t id; if (pthread_create(&id, NULL, (void *(*)(void *))accyProtDaemon, NULL) != 0) { DBG_ERROR("Pthread create failed. errno = %s", strerror(errno)); return 0; } return 1; } /** Responsible for protocol communication */ static void accyProtDaemon(void *arg) { struct sched_param sched; int currentPolicy, tryWakeup, tryComm; pthread_t threadId; uint8_t wakeupSuccess; unsigned int dockDetails; uint8_t dockType = NO_DOCK; int status; struct timespec ts; struct timeval tv; uint8_t statusFuse[8], FSNo[8], RomSN[8], RomRNo[8]; char devInfo[32]; char devProp[8]; threadId = pthread_self(); status = pthread_getschedparam(threadId, ¤tPolicy, &sched); if (status != 0) { DBG_ERROR("pthread_getschedparam error. erno = %s", strerror(status)); return; } currentPolicy = SCHED_RR; sched.sched_priority = 70; status = pthread_setschedparam(threadId, currentPolicy, &sched); if (status != 0) { DBG_ERROR("pthread_setschedparam error. erno = %s", strerror(status)); return; } switchUser(); while(1) { do { status = sem_wait(&SigAccyProtStart); } while (status < 0 && errno == EINTR); if (status == -1) { DBG_ERROR("SEM WAIT failed with -1. errno = %s", strerror(errno)); break; } /* If already undocked, why do anything */ if (globalState == GLOBAL_STATE_UNDOCKED) { DBG_TRACE("Thread. GLOBAL_STATE_UNDOCKED"); continue; } if (globalProtocol == PROTOCOL_UART) { doIoctl(CPCAP_IOCTL_ACCY_WHISPER, CPCAP_WHISPER_ENABLE_UART, NULL, NULL); } wakeLock = acquire_wake_lock(PARTIAL_WAKE_LOCK, wakeLockString); tryComm = 1; while (tryComm && (globalState == GLOBAL_STATE_DOCKED)) { if (globalProtocol == PROTOCOL_UART) { if (SHA_SUCCESS == SHAP_OpenChannel()) { tryWakeup = 1; wakeupSuccess = 0; while (tryWakeup) { if (SHAC_Wakeup() == SHA_SUCCESS) { DBG_TRACE("WAKEUP SUCCESS %d ", tryWakeup); tryWakeup = 0; wakeupSuccess = 1; } else { if (tryWakeup == MAX_TRY_WAKEUP) { DBG_ERROR("GIVING UP WAKEUP after %d tries", tryWakeup); tryWakeup = 0; } else { DBG_TRACE("TRYING WAKEUP ONCE MORE"); ts.tv_sec = 0; ts.tv_nsec = 10000000; // 10 ms nanosleep(&ts, NULL); tryWakeup++; } } if (globalState == GLOBAL_STATE_UNDOCKED) { tryWakeup = 0; } } if ((wakeupSuccess) && (globalState == GLOBAL_STATE_DOCKED)) { DBG_TRACE("Reading Status & MfgId"); // Read Status fuses and MfgId fuses status = SHAC_Read(0x01, 0x0002); if (status == SHA_SUCCESS) { copyResults(8, statusFuse); // TODO: bytes in wrong order for some reason?? uint8_t temp[2]; temp[0] = statusFuse[1]; temp[1] = statusFuse[3]; statusFuse[1] = temp[1]; statusFuse[3] = temp[0]; } if (globalState == GLOBAL_STATE_DOCKED && status == SHA_SUCCESS) { DBG_TRACE("Reading Serial Number"); // Read Fuse Serial number value status = SHAC_Read(0x01, 0x0003); if (status == SHA_SUCCESS) { copyResults(8, FSNo); } } if (globalState == GLOBAL_STATE_DOCKED && status == SHA_SUCCESS) { DBG_TRACE("Reading ROM MfgId and ROM SN"); // ROM MfgId and ROM SN status = SHAC_Read(0x00, 0x0000); if (status == SHA_SUCCESS) { copyResults(8, RomSN); DBG_TRACE("Authentication succeed"); globalState = GLOBAL_STATE_DOCKED_IDSUCC; } } } } SHAP_CloseChannel(); } else if (globalProtocol == PROTOCOL_HID) { uint8_t writebuff[65] = {0x0}; uint8_t readbuff[65] = {0x0}; uint8_t displaybuff[65] = {0x0}; int hidStatus; DBG_TRACE("HID: Sending status query"); hidStatus = HID_FAILURE; memset(writebuff,0x00,sizeof(writebuff)); memcpy(writebuff, hidStatusQuery, sizeof(hidStatusQuery)); status = write(hidFd, writebuff, HID_STATUS_QUERY_LENGTH); if (status != HID_STATUS_QUERY_LENGTH) { DBG_ERROR("Failed writing status query (errno = %s)", strerror(errno)); } else { hidStatus = HID_SUCCESS; } if (globalState == GLOBAL_STATE_DOCKED && hidStatus == HID_SUCCESS) { DBG_TRACE("HID: Reading status query response"); status = read(hidFd, readbuff, HID_STATUS_MSG_LENGTH); if (status != HID_STATUS_MSG_LENGTH) { DBG_ERROR("HID: Failed reading status query response, errno = %s", strerror(errno)); hidStatus = HID_FAILURE; } else { DBG_TRACE("Contents of receive buffer"); DBG_TRACE("first 3 bytes: %02X%02X%02X", readbuff[0], readbuff[1], readbuff[2]); DBG_TRACE("Status: %02X%02X", readbuff[3], readbuff[4]); DBG_TRACE("Ref Num: %02X%02X", readbuff[5], readbuff[6]); DBG_TRACE("Version: %s", &readbuff[6]); } } if (globalState == GLOBAL_STATE_DOCKED && hidStatus == HID_SUCCESS) { DBG_TRACE("HID: Sending ID query"); memset(writebuff,0x00,sizeof(writebuff)); memcpy(writebuff, hidIdQuery, sizeof(hidIdQuery)); status = write(hidFd, writebuff, HID_ID_QUERY_LENGTH); if (status != HID_ID_QUERY_LENGTH) { DBG_ERROR("HID: Error writing ID query, %d", status); hidStatus = HID_FAILURE; } } if (globalState == GLOBAL_STATE_DOCKED && hidStatus == HID_SUCCESS) { DBG_TRACE("Reading ID query response"); status = read(hidFd, readbuff, HID_ID_MSG_LENGTH); if (status != HID_ID_MSG_LENGTH) { DBG_ERROR("HID: Error reading ID query response, errno = %s", strerror(errno)); hidStatus = HID_FAILURE; } else { DBG_TRACE("Contents of receive buffer"); DBG_TRACE("first 3-2 bytes: %02X%02X%02X", readbuff[0], readbuff[1], readbuff[2]); DBG_TRACE("Status: %02X%02X", readbuff[3], readbuff[4]); DBG_TRACE("Ref Num: %02X%02X", readbuff[5], readbuff[6]); DBG_TRACE("SEMU ID: %02X%02X%02X", readbuff[7], readbuff[8], readbuff[9]); DBG_TRACE("Manufacturer ID: %02X", readbuff[10]); DBG_TRACE("ROM Revision: %02X%02X%02X%02X", readbuff[11], readbuff[12], readbuff[13], readbuff[14]); DBG_TRACE("Fuse SN: %02X%02X%02X%02X", readbuff[15], readbuff[16], readbuff[17], readbuff[18]); DBG_TRACE("ROM SN: %02X%02X", readbuff[19], readbuff[20]); statusFuse[1] = readbuff[6]; statusFuse[2] = readbuff[7]; statusFuse[3] = readbuff[8]; FSNo[1] = readbuff[14]; FSNo[2] = readbuff[15]; FSNo[3] = readbuff[16]; FSNo[4] = readbuff[17]; RomSN[3] = readbuff[18]; RomSN[4] = readbuff[19]; globalState = GLOBAL_STATE_DOCKED_IDSUCC; } } } if (globalState == GLOBAL_STATE_DOCKED_IDSUCC) { dockType = NO_DOCK; if (statusFuse[1] == 0x0A && statusFuse[2] == 0xC0) { dockType = LE_DOCK; DBG_TRACE("It's a Low End Dock"); } else if (statusFuse[1] == 0x12 && statusFuse[2] == 0xC0 && statusFuse[3] == 0x00) { dockType = CAR_DOCK; DBG_TRACE("It's a Car Dock"); } else if (statusFuse[1] == 0x1A && statusFuse[2] == 0x80 && statusFuse[3] == 0x00) { dockType = HE_DOCK; DBG_TRACE("It's a High End Dock"); } /* Format the output */ createOutput(&FSNo[1],&devInfo[0], 4); createOutput(&RomSN[3],&devInfo[8], 2); devInfo[12] = 0; createOutput(&statusFuse[1], &devProp[0], 3); devProp[6] = 0; DBG_TRACE("ID SUCCESS %s", devInfo); if(dockType == NO_DOCK) DBG_TRACE("UNKNOWN STATUS FUSES <%d><%d><%d>\n", statusFuse[1], statusFuse[2], statusFuse[3]); tryComm = 0; dockDetails = ID_SUCCESS; dockDetails |= (dockType << DOCK_TYPE_OFFSET); doIoctl(CPCAP_IOCTL_ACCY_WHISPER, dockDetails, devInfo, devProp); globalState = GLOBAL_STATE_DOCKED_IDSUCC; memset(devInfo,0x00,sizeof(devInfo)); memset(devProp,0x00,sizeof(devProp)); } /* if the global state is still docked, then increment the retry counter */ if (globalState == GLOBAL_STATE_DOCKED) { if (tryComm == MAX_TRY_COMM) { DBG_ERROR("GIVING UP AFTER %d tries", tryComm); tryComm = 0; dockDetails = 0; // set bit 0, for AUTH to be failure doIoctl(CPCAP_IOCTL_ACCY_WHISPER, dockDetails, NULL, NULL); globalState = GLOBAL_STATE_DOCKED_IDFAIL; } else { ts.tv_sec = 0; ts.tv_nsec = 100000000; // 100 ms nanosleep(&ts, NULL); tryComm++; DBG_TRACE("Trying COMM %d time", tryComm); } } } if (wakeLock) { release_wake_lock(wakeLockString); wakeLock = 0; } } } static void createOutput(uint8_t *inp, char *out, int bytes) { int i, j; for (i = 0; i < bytes; i++) { j = (inp[i] & 0x0F); out[i*2+1] = xintToChar[j]; j = ((inp[i] >> 4) & 0xFF); out[i*2] = xintToChar[j]; } } static void accySigHandler(signed int signal) { switch(signal) { case SIGINT: case SIGKILL: case SIGTERM: /* cose fds */ if (cpcapFd > 0) close(cpcapFd); if (ttyFd > 0) close(ttyFd); if (hidFd > 0) close(hidFd); exit(0); break; default: break; } } static int accyInit(void) { struct sockaddr_nl addr; int sz = 64*1024; int s; struct sigaction shutdownAction; struct stat statBuf; #if defined(LOG_ACCY_FS) if(stat(LOG_FILE_PATH, &statBuf) == 0) { logFp = fopen(LOG_FILE_NAME, "w"); if (logFp == NULL) { ALOGE("whisperd: Unable to open the Logfile %s", LOG_FILE_NAME); } } #endif cpcapFd = open("/dev/cpcap", O_RDWR); if (cpcapFd == -1) { DBG_ERROR("/dev/cpcap could not be opened. err = %s", strerror(errno)); } else { DBG_TRACE("/dev/cpcap opened: %d", cpcapFd); } /* Setup the shutdown action. */ shutdownAction.sa_handler = accySigHandler; sigemptyset(&shutdownAction.sa_mask); shutdownAction.sa_flags = 0; /* Setup the signal handler. */ sigaction(SIGINT, &shutdownAction, NULL); sigaction(SIGKILL, &shutdownAction, NULL); sigaction(SIGTERM, &shutdownAction, NULL); globalState = GLOBAL_STATE_UNDOCKED; memset(&addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; addr.nl_pid = getpid(); addr.nl_groups = 0xffffffff; s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); if (s < 0) { DBG_ERROR("Socket failed. err = %s", strerror(errno)); return 0; } setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)); if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { DBG_ERROR("Bind failed. errno = %d", errno); close(s); return 0; } ueventFd = s; return (ueventFd > 0); } void doIoctl(int cmd, unsigned int data, char *dev_id, char *dev_prop) { int i, status = -1; struct timespec ts; struct cpcap_whisper_request req; memset(req.dock_id, 0, CPCAP_WHISPER_ID_SIZE); req.cmd = data; if(dev_id != NULL) strcpy(req.dock_id, dev_id); if(dev_prop != NULL) strcpy(req.dock_prop, dev_prop); for (i = 0; i < MAX_TRY_IOCTL; i++) { DBG_TRACE("ioctl cmd %d: %d,", cmd, data); if(dev_id == NULL) { DBG_TRACE("ioctl id = NULL"); } else { DBG_TRACE("ioctl id = <%s>\n", req.dock_id); } status = ioctl(cpcapFd, cmd, &req); if (status != IOCTL_SUCCESS) { DBG_ERROR("ioctl returned %d with error: %d", status, errno); ts.tv_sec = 0; ts.tv_nsec = 50000000; // 50 ms DBG_TRACE("Wait 50ms."); nanosleep(&ts, NULL); globalState = GLOBAL_STATE_UNDOCKED; } else { DBG_TRACE("ioctl success"); globalState = GLOBAL_STATE_DOCKED; break; } } if (hidFd > 0) { close(hidFd); hidFd = -1; } return; } static void copyResults(int8_t cmdSize, uint8_t *out) { int i; char charOut[128]; mainparms = SHAC_GetData(); createOutput(mainparms->txBuffer, charOut, cmdSize); charOut[cmdSize*2] = '\0'; DBG_TRACE("Send Value: %s", charOut); for(i = 0; i < mainparms->rxSize; i++) { out[i] = mainparms->rxBuffer[i]; } createOutput(mainparms->rxBuffer, charOut, cmdSize); charOut[cmdSize*2] = '\0'; DBG_TRACE("Receive Value: = %s", charOut); } int main(int argc, char *argv[]) { int retVal; retVal = accyInit(); if (retVal <= 0) { DBG_ERROR("accyInit failed"); } if (sem_init(&SigAccyProtStart, 0, 0) != 0) { DBG_ERROR("Sem_init failed. errno = %d", errno); } //TODO: First time failure to set parameters SHAP_OpenChannel(); SHAP_CloseFile(); retVal = accySpawnThread(); switchUser(); waitForUevents(); return 1; } int switchUser( void ) { int status; status = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); if (status < 0) { return status; } status = setuid(AID_RADIO); if (status < 0) { return status; } struct __user_cap_header_struct header; struct __user_cap_data_struct cap; header.version = _LINUX_CAPABILITY_VERSION; header.pid = 0; cap.effective = cap.permitted = 1 << CAP_NET_ADMIN; cap.inheritable = 0; status = capset(&header, &cap); return status; }