| /* |
| * Copyright (C) 2010 NXP Semiconductors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * \file phDalNfc_uart.c |
| * \brief DAL com port implementation for linux |
| * |
| * Project: Trusted NFC Linux Lignt |
| * |
| * $Date: 07 aug 2009 |
| * $Author: Jonathan roux |
| * $Revision: 1.0 $ |
| * |
| */ |
| |
| #define LOG_TAG "NFC_uart" |
| #include <cutils/log.h> |
| #include <hardware/nfc.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <termios.h> |
| #include <errno.h> |
| #include <sys/ioctl.h> |
| #include <sys/select.h> |
| #include <stdio.h> |
| #include <errno.h> |
| |
| #include <phDal4Nfc_debug.h> |
| #include <phDal4Nfc_uart.h> |
| #include <phOsalNfc.h> |
| #include <phNfcStatus.h> |
| #if defined(ANDROID) |
| #include <string.h> |
| #include <cutils/properties.h> // for property_get |
| #endif |
| |
| typedef struct |
| { |
| int nHandle; |
| char nOpened; |
| struct termios nIoConfigBackup; |
| struct termios nIoConfig; |
| |
| } phDal4Nfc_ComPortContext_t; |
| |
| /*----------------------------------------------------------------------------------- |
| COM PORT CONFIGURATION |
| ------------------------------------------------------------------------------------*/ |
| #define DAL_BAUD_RATE B115200 |
| |
| |
| |
| /*----------------------------------------------------------------------------------- |
| VARIABLES |
| ------------------------------------------------------------------------------------*/ |
| static phDal4Nfc_ComPortContext_t gComPortContext; |
| |
| |
| |
| /*----------------------------------------------------------------------------- |
| |
| FUNCTION: phDal4Nfc_uart_set_open_from_handle |
| |
| PURPOSE: Initialize internal variables |
| |
| -----------------------------------------------------------------------------*/ |
| |
| void phDal4Nfc_uart_initialize(void) |
| { |
| memset(&gComPortContext, 0, sizeof(phDal4Nfc_ComPortContext_t)); |
| } |
| |
| |
| /*----------------------------------------------------------------------------- |
| |
| FUNCTION: phDal4Nfc_uart_set_open_from_handle |
| |
| PURPOSE: The application could have opened the link itself. So we just need |
| to get the handle and consider that the open operation has already |
| been done. |
| |
| -----------------------------------------------------------------------------*/ |
| |
| void phDal4Nfc_uart_set_open_from_handle(phHal_sHwReference_t * pDalHwContext) |
| { |
| gComPortContext.nHandle = (int) pDalHwContext->p_board_driver; |
| DAL_ASSERT_STR(gComPortContext.nHandle >= 0, "Bad passed com port handle"); |
| gComPortContext.nOpened = 1; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| |
| FUNCTION: phDal4Nfc_uart_is_opened |
| |
| PURPOSE: Returns if the link is opened or not. (0 = not opened; 1 = opened) |
| |
| -----------------------------------------------------------------------------*/ |
| |
| int phDal4Nfc_uart_is_opened(void) |
| { |
| return gComPortContext.nOpened; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| |
| FUNCTION: phDal4Nfc_uart_flush |
| |
| PURPOSE: Flushes the link ; clears the link buffers |
| |
| -----------------------------------------------------------------------------*/ |
| |
| void phDal4Nfc_uart_flush(void) |
| { |
| int ret; |
| /* flushes the com port */ |
| ret = tcflush(gComPortContext.nHandle, TCIFLUSH); |
| DAL_ASSERT_STR(ret!=-1, "tcflush failed"); |
| } |
| |
| /*----------------------------------------------------------------------------- |
| |
| FUNCTION: phDal4Nfc_uart_close |
| |
| PURPOSE: Closes the link |
| |
| -----------------------------------------------------------------------------*/ |
| |
| void phDal4Nfc_uart_close(void) |
| { |
| if (gComPortContext.nOpened == 1) |
| { |
| close(gComPortContext.nHandle); |
| gComPortContext.nHandle = 0; |
| gComPortContext.nOpened = 0; |
| } |
| } |
| |
| /*----------------------------------------------------------------------------- |
| |
| FUNCTION: phDal4Nfc_uart_close |
| |
| PURPOSE: Closes the link |
| |
| -----------------------------------------------------------------------------*/ |
| |
| NFCSTATUS phDal4Nfc_uart_open_and_configure(pphDal4Nfc_sConfig_t pConfig, void ** pLinkHandle) |
| { |
| int nComStatus; |
| NFCSTATUS nfcret = NFCSTATUS_SUCCESS; |
| int ret; |
| |
| DAL_ASSERT_STR(gComPortContext.nOpened==0, "Trying to open but already done!"); |
| |
| srand(time(NULL)); |
| |
| /* open communication port handle */ |
| gComPortContext.nHandle = open(pConfig->deviceNode, O_RDWR | O_NOCTTY); |
| if (gComPortContext.nHandle < 0) |
| { |
| *pLinkHandle = NULL; |
| return PHNFCSTVAL(CID_NFC_DAL, NFCSTATUS_INVALID_DEVICE); |
| } |
| |
| gComPortContext.nOpened = 1; |
| *pLinkHandle = (void*)gComPortContext.nHandle; |
| |
| /* |
| * Now configure the com port |
| */ |
| ret = tcgetattr(gComPortContext.nHandle, &gComPortContext.nIoConfigBackup); /* save the old io config */ |
| if (ret == -1) |
| { |
| /* tcgetattr failed -- it is likely that the provided port is invalid */ |
| *pLinkHandle = NULL; |
| return PHNFCSTVAL(CID_NFC_DAL, NFCSTATUS_INVALID_DEVICE); |
| } |
| ret = fcntl(gComPortContext.nHandle, F_SETFL, 0); /* Makes the read blocking (default). */ |
| DAL_ASSERT_STR(ret != -1, "fcntl failed"); |
| /* Configures the io */ |
| memset((void *)&gComPortContext.nIoConfig, (int)0, (size_t)sizeof(struct termios)); |
| /* |
| BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed. |
| CRTSCTS : output hardware flow control (only used if the cable has |
| all necessary lines. See sect. 7 of Serial-HOWTO) |
| CS8 : 8n1 (8bit,no parity,1 stopbit) |
| CLOCAL : local connection, no modem contol |
| CREAD : enable receiving characters |
| */ |
| gComPortContext.nIoConfig.c_cflag = DAL_BAUD_RATE | CS8 | CLOCAL | CREAD; /* Control mode flags */ |
| gComPortContext.nIoConfig.c_iflag = IGNPAR; /* Input mode flags : IGNPAR Ignore parity errors */ |
| gComPortContext.nIoConfig.c_oflag = 0; /* Output mode flags */ |
| gComPortContext.nIoConfig.c_lflag = 0; /* Local mode flags. Read mode : non canonical, no echo */ |
| gComPortContext.nIoConfig.c_cc[VTIME] = 0; /* Control characters. No inter-character timer */ |
| gComPortContext.nIoConfig.c_cc[VMIN] = 1; /* Control characters. Read is blocking until X characters are read */ |
| |
| /* |
| TCSANOW Make changes now without waiting for data to complete |
| TCSADRAIN Wait until everything has been transmitted |
| TCSAFLUSH Flush input and output buffers and make the change |
| */ |
| ret = tcsetattr(gComPortContext.nHandle, TCSANOW, &gComPortContext.nIoConfig); |
| DAL_ASSERT_STR(ret != -1, "tcsetattr failed"); |
| |
| /* |
| On linux the DTR signal is set by default. That causes a problem for pn544 chip |
| because this signal is connected to "reset". So we clear it. (on windows it is cleared by default). |
| */ |
| ret = ioctl(gComPortContext.nHandle, TIOCMGET, &nComStatus); |
| DAL_ASSERT_STR(ret != -1, "ioctl TIOCMGET failed"); |
| nComStatus &= ~TIOCM_DTR; |
| ret = ioctl(gComPortContext.nHandle, TIOCMSET, &nComStatus); |
| DAL_ASSERT_STR(ret != -1, "ioctl TIOCMSET failed"); |
| DAL_DEBUG("Com port status=%d\n", nComStatus); |
| usleep(10000); /* Mandatory sleep so that the DTR line is ready before continuing */ |
| |
| return nfcret; |
| } |
| |
| /* |
| adb shell setprop debug.nfc.UART_ERROR_RATE X |
| will corrupt and drop bytes in uart_read(), to test the error handling |
| of DAL & LLC errors. |
| */ |
| int property_error_rate = 0; |
| static void read_property() { |
| char value[PROPERTY_VALUE_MAX]; |
| property_get("debug.nfc.UART_ERROR_RATE", value, "0"); |
| property_error_rate = atoi(value); |
| } |
| |
| /* returns length of buffer after errors */ |
| static int apply_errors(uint8_t *buffer, int length) { |
| int i; |
| if (!property_error_rate) return length; |
| |
| for (i = 0; i < length; i++) { |
| if (rand() % 1000 < property_error_rate) { |
| if (rand() % 2) { |
| // 50% chance of dropping byte |
| length--; |
| memcpy(&buffer[i], &buffer[i+1], length-i); |
| LOGW("dropped byte %d", i); |
| } else { |
| // 50% chance of corruption |
| buffer[i] = (uint8_t)rand(); |
| LOGW("corrupted byte %d", i); |
| } |
| } |
| } |
| return length; |
| } |
| |
| static struct timeval timeval_remaining(struct timespec timeout) { |
| struct timespec now; |
| struct timeval delta; |
| clock_gettime(CLOCK_MONOTONIC, &now); |
| |
| delta.tv_sec = timeout.tv_sec - now.tv_sec; |
| delta.tv_usec = (timeout.tv_nsec - now.tv_nsec) / (long)1000; |
| |
| if (delta.tv_usec < 0) { |
| delta.tv_usec += 1000000; |
| delta.tv_sec--; |
| } |
| if (delta.tv_sec < 0) { |
| delta.tv_sec = 0; |
| delta.tv_usec = 0; |
| } |
| return delta; |
| } |
| |
| static int libnfc_firmware_mode = 0; |
| |
| /*----------------------------------------------------------------------------- |
| |
| FUNCTION: phDal4Nfc_uart_read |
| |
| PURPOSE: Reads nNbBytesToRead bytes and writes them in pBuffer. |
| Returns the number of bytes really read or -1 in case of error. |
| |
| -----------------------------------------------------------------------------*/ |
| int phDal4Nfc_uart_read(uint8_t * pBuffer, int nNbBytesToRead) |
| { |
| int ret; |
| int numRead = 0; |
| struct timeval tv; |
| struct timeval *ptv; |
| struct timespec timeout; |
| fd_set rfds; |
| |
| DAL_ASSERT_STR(gComPortContext.nOpened == 1, "read called but not opened!"); |
| DAL_DEBUG("_uart_read() called to read %d bytes", nNbBytesToRead); |
| |
| read_property(); |
| |
| // Read timeout: |
| // FW mode: 10s timeout |
| // 1 byte read: steady-state LLC length read, allowed to block forever |
| // >1 byte read: LLC payload, 100ms timeout (before pn544 re-transmit) |
| if (nNbBytesToRead > 1 && !libnfc_firmware_mode) { |
| clock_gettime(CLOCK_MONOTONIC, &timeout); |
| timeout.tv_nsec += 100000000; |
| if (timeout.tv_nsec > 1000000000) { |
| timeout.tv_sec++; |
| timeout.tv_nsec -= 1000000000; |
| } |
| ptv = &tv; |
| } else if (libnfc_firmware_mode) { |
| clock_gettime(CLOCK_MONOTONIC, &timeout); |
| timeout.tv_sec += 10; |
| ptv = &tv; |
| } else { |
| ptv = NULL; |
| } |
| |
| while (numRead < nNbBytesToRead) { |
| FD_ZERO(&rfds); |
| FD_SET(gComPortContext.nHandle, &rfds); |
| |
| if (ptv) { |
| tv = timeval_remaining(timeout); |
| ptv = &tv; |
| } |
| |
| ret = select(gComPortContext.nHandle + 1, &rfds, NULL, NULL, ptv); |
| if (ret < 0) { |
| DAL_DEBUG("select() errno=%d", errno); |
| if (errno == EINTR || errno == EAGAIN) { |
| continue; |
| } |
| return -1; |
| } else if (ret == 0) { |
| LOGW("timeout!"); |
| break; // return partial response |
| } |
| ret = read(gComPortContext.nHandle, pBuffer + numRead, nNbBytesToRead - numRead); |
| if (ret > 0) { |
| ret = apply_errors(pBuffer + numRead, ret); |
| |
| DAL_DEBUG("read %d bytes", ret); |
| numRead += ret; |
| } else if (ret == 0) { |
| DAL_PRINT("_uart_read() EOF"); |
| return 0; |
| } else { |
| DAL_DEBUG("_uart_read() errno=%d", errno); |
| if (errno == EINTR || errno == EAGAIN) { |
| continue; |
| } |
| return -1; |
| } |
| } |
| |
| return numRead; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| |
| FUNCTION: phDal4Nfc_link_write |
| |
| PURPOSE: Writes nNbBytesToWrite bytes from pBuffer to the link |
| Returns the number of bytes that have been wrote to the interface or -1 in case of error. |
| |
| -----------------------------------------------------------------------------*/ |
| |
| int phDal4Nfc_uart_write(uint8_t * pBuffer, int nNbBytesToWrite) |
| { |
| int ret; |
| int numWrote = 0; |
| |
| DAL_ASSERT_STR(gComPortContext.nOpened == 1, "write called but not opened!"); |
| DAL_DEBUG("_uart_write() called to write %d bytes\n", nNbBytesToWrite); |
| |
| while (numWrote < nNbBytesToWrite) { |
| ret = write(gComPortContext.nHandle, pBuffer + numWrote, nNbBytesToWrite - numWrote); |
| if (ret > 0) { |
| DAL_DEBUG("wrote %d bytes", ret); |
| numWrote += ret; |
| } else if (ret == 0) { |
| DAL_PRINT("_uart_write() EOF"); |
| return -1; |
| } else { |
| DAL_DEBUG("_uart_write() errno=%d", errno); |
| if (errno == EINTR || errno == EAGAIN) { |
| continue; |
| } |
| return -1; |
| } |
| } |
| |
| return numWrote; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| |
| FUNCTION: phDal4Nfc_uart_reset |
| |
| PURPOSE: Reset the PN544, using the VEN pin |
| |
| -----------------------------------------------------------------------------*/ |
| int phDal4Nfc_uart_reset(long level) |
| { |
| static const char NFC_POWER_PATH[] = "/sys/devices/platform/nfc-power/nfc_power"; |
| int sz; |
| int fd = -1; |
| int ret = NFCSTATUS_FAILED; |
| char buffer[2]; |
| |
| DAL_DEBUG("phDal4Nfc_uart_reset, VEN level = %ld", level); |
| |
| if (snprintf(buffer, sizeof(buffer), "%u", (unsigned int)level) != 1) { |
| LOGE("Bad nfc power level (%u)", (unsigned int)level); |
| goto out; |
| } |
| |
| fd = open(NFC_POWER_PATH, O_WRONLY); |
| if (fd < 0) { |
| LOGE("open(%s) for write failed: %s (%d)", NFC_POWER_PATH, |
| strerror(errno), errno); |
| goto out; |
| } |
| sz = write(fd, &buffer, sizeof(buffer) - 1); |
| if (sz < 0) { |
| LOGE("write(%s) failed: %s (%d)", NFC_POWER_PATH, strerror(errno), |
| errno); |
| goto out; |
| } |
| ret = NFCSTATUS_SUCCESS; |
| if (level == 2) { |
| libnfc_firmware_mode = 1; |
| } else { |
| libnfc_firmware_mode = 0; |
| } |
| |
| out: |
| if (fd >= 0) { |
| close(fd); |
| } |
| return ret; |
| } |