454 lines
14 KiB
C
454 lines
14 KiB
C
/*
|
|
* 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)(intptr_t) 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*)(intptr_t)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);
|
|
ALOGW("dropped byte %d", i);
|
|
} else {
|
|
// 50% chance of corruption
|
|
buffer[i] = (uint8_t)rand();
|
|
ALOGW("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) {
|
|
ALOGW("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) {
|
|
ALOGE("Bad nfc power level (%u)", (unsigned int)level);
|
|
goto out;
|
|
}
|
|
|
|
fd = open(NFC_POWER_PATH, O_WRONLY);
|
|
if (fd < 0) {
|
|
ALOGE("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) {
|
|
ALOGE("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;
|
|
}
|