1/*
2 * Copyright (C) 2010 NXP Semiconductors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/**
18 * \file phDalNfc_uart.c
19 * \brief DAL com port implementation for linux
20 *
21 * Project: Trusted NFC Linux Lignt
22 *
23 * $Date: 07 aug 2009
24 * $Author: Jonathan roux
25 * $Revision: 1.0 $
26 *
27 */
28
29#define LOG_TAG "NFC_uart"
30#include <cutils/log.h>
31#include <hardware/nfc.h>
32#include <stdlib.h>
33#include <unistd.h>
34#include <fcntl.h>
35#include <termios.h>
36#include <errno.h>
37#include <sys/ioctl.h>
38#include <sys/select.h>
39#include <stdio.h>
40#include <errno.h>
41
42#include <phDal4Nfc_debug.h>
43#include <phDal4Nfc_uart.h>
44#include <phOsalNfc.h>
45#include <phNfcStatus.h>
46#if defined(ANDROID)
47#include <string.h>
48#include <cutils/properties.h> // for property_get
49#endif
50
51typedef struct
52{
53   int  nHandle;
54   char nOpened;
55   struct termios nIoConfigBackup;
56   struct termios nIoConfig;
57
58} phDal4Nfc_ComPortContext_t;
59
60/*-----------------------------------------------------------------------------------
61                                COM PORT CONFIGURATION
62------------------------------------------------------------------------------------*/
63#define DAL_BAUD_RATE  B115200
64
65
66
67/*-----------------------------------------------------------------------------------
68                                      VARIABLES
69------------------------------------------------------------------------------------*/
70static phDal4Nfc_ComPortContext_t gComPortContext;
71
72
73
74/*-----------------------------------------------------------------------------
75
76FUNCTION: phDal4Nfc_uart_set_open_from_handle
77
78PURPOSE:  Initialize internal variables
79
80-----------------------------------------------------------------------------*/
81
82void phDal4Nfc_uart_initialize(void)
83{
84   memset(&gComPortContext, 0, sizeof(phDal4Nfc_ComPortContext_t));
85}
86
87
88/*-----------------------------------------------------------------------------
89
90FUNCTION: phDal4Nfc_uart_set_open_from_handle
91
92PURPOSE:  The application could have opened the link itself. So we just need
93          to get the handle and consider that the open operation has already
94          been done.
95
96-----------------------------------------------------------------------------*/
97
98void phDal4Nfc_uart_set_open_from_handle(phHal_sHwReference_t * pDalHwContext)
99{
100   gComPortContext.nHandle = (int) pDalHwContext->p_board_driver;
101   DAL_ASSERT_STR(gComPortContext.nHandle >= 0, "Bad passed com port handle");
102   gComPortContext.nOpened = 1;
103}
104
105/*-----------------------------------------------------------------------------
106
107FUNCTION: phDal4Nfc_uart_is_opened
108
109PURPOSE:  Returns if the link is opened or not. (0 = not opened; 1 = opened)
110
111-----------------------------------------------------------------------------*/
112
113int phDal4Nfc_uart_is_opened(void)
114{
115   return gComPortContext.nOpened;
116}
117
118/*-----------------------------------------------------------------------------
119
120FUNCTION: phDal4Nfc_uart_flush
121
122PURPOSE:  Flushes the link ; clears the link buffers
123
124-----------------------------------------------------------------------------*/
125
126void phDal4Nfc_uart_flush(void)
127{
128   int ret;
129   /* flushes the com port */
130   ret = tcflush(gComPortContext.nHandle, TCIFLUSH);
131   DAL_ASSERT_STR(ret!=-1, "tcflush failed");
132}
133
134/*-----------------------------------------------------------------------------
135
136FUNCTION: phDal4Nfc_uart_close
137
138PURPOSE:  Closes the link
139
140-----------------------------------------------------------------------------*/
141
142void phDal4Nfc_uart_close(void)
143{
144   if (gComPortContext.nOpened == 1)
145   {
146      close(gComPortContext.nHandle);
147      gComPortContext.nHandle = 0;
148      gComPortContext.nOpened = 0;
149   }
150}
151
152/*-----------------------------------------------------------------------------
153
154FUNCTION: phDal4Nfc_uart_close
155
156PURPOSE:  Closes the link
157
158-----------------------------------------------------------------------------*/
159
160NFCSTATUS phDal4Nfc_uart_open_and_configure(pphDal4Nfc_sConfig_t pConfig, void ** pLinkHandle)
161{
162   int          nComStatus;
163   NFCSTATUS    nfcret = NFCSTATUS_SUCCESS;
164   int          ret;
165
166   DAL_ASSERT_STR(gComPortContext.nOpened==0, "Trying to open but already done!");
167
168   srand(time(NULL));
169
170   /* open communication port handle */
171   gComPortContext.nHandle = open(pConfig->deviceNode, O_RDWR | O_NOCTTY);
172   if (gComPortContext.nHandle < 0)
173   {
174      *pLinkHandle = NULL;
175      return PHNFCSTVAL(CID_NFC_DAL, NFCSTATUS_INVALID_DEVICE);
176   }
177
178   gComPortContext.nOpened = 1;
179   *pLinkHandle = (void*)gComPortContext.nHandle;
180
181   /*
182    *  Now configure the com port
183    */
184   ret = tcgetattr(gComPortContext.nHandle, &gComPortContext.nIoConfigBackup); /* save the old io config */
185   if (ret == -1)
186   {
187      /* tcgetattr failed -- it is likely that the provided port is invalid */
188      *pLinkHandle = NULL;
189      return PHNFCSTVAL(CID_NFC_DAL, NFCSTATUS_INVALID_DEVICE);
190   }
191   ret = fcntl(gComPortContext.nHandle, F_SETFL, 0); /* Makes the read blocking (default).  */
192   DAL_ASSERT_STR(ret != -1, "fcntl failed");
193   /* Configures the io */
194   memset((void *)&gComPortContext.nIoConfig, (int)0, (size_t)sizeof(struct termios));
195   /*
196    BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed.
197    CRTSCTS : output hardware flow control (only used if the cable has
198              all necessary lines. See sect. 7 of Serial-HOWTO)
199    CS8     : 8n1 (8bit,no parity,1 stopbit)
200    CLOCAL  : local connection, no modem contol
201    CREAD   : enable receiving characters
202   */
203   gComPortContext.nIoConfig.c_cflag = DAL_BAUD_RATE | CS8 | CLOCAL | CREAD;  /* Control mode flags */
204   gComPortContext.nIoConfig.c_iflag = IGNPAR;                                          /* Input   mode flags : IGNPAR  Ignore parity errors */
205   gComPortContext.nIoConfig.c_oflag = 0;                                               /* Output  mode flags */
206   gComPortContext.nIoConfig.c_lflag = 0;                                               /* Local   mode flags. Read mode : non canonical, no echo */
207   gComPortContext.nIoConfig.c_cc[VTIME] = 0;                                           /* Control characters. No inter-character timer */
208   gComPortContext.nIoConfig.c_cc[VMIN]  = 1;                                           /* Control characters. Read is blocking until X characters are read */
209
210   /*
211      TCSANOW  Make changes now without waiting for data to complete
212      TCSADRAIN   Wait until everything has been transmitted
213      TCSAFLUSH   Flush input and output buffers and make the change
214   */
215   ret = tcsetattr(gComPortContext.nHandle, TCSANOW, &gComPortContext.nIoConfig);
216   DAL_ASSERT_STR(ret != -1, "tcsetattr failed");
217
218   /*
219      On linux the DTR signal is set by default. That causes a problem for pn544 chip
220      because this signal is connected to "reset". So we clear it. (on windows it is cleared by default).
221   */
222   ret = ioctl(gComPortContext.nHandle, TIOCMGET, &nComStatus);
223   DAL_ASSERT_STR(ret != -1, "ioctl TIOCMGET failed");
224   nComStatus &= ~TIOCM_DTR;
225   ret = ioctl(gComPortContext.nHandle, TIOCMSET, &nComStatus);
226   DAL_ASSERT_STR(ret != -1, "ioctl TIOCMSET failed");
227   DAL_DEBUG("Com port status=%d\n", nComStatus);
228   usleep(10000); /* Mandatory sleep so that the DTR line is ready before continuing */
229
230   return nfcret;
231}
232
233/*
234  adb shell setprop debug.nfc.UART_ERROR_RATE X
235  will corrupt and drop bytes in uart_read(), to test the error handling
236  of DAL & LLC errors.
237 */
238int property_error_rate = 0;
239static void read_property() {
240    char value[PROPERTY_VALUE_MAX];
241    property_get("debug.nfc.UART_ERROR_RATE", value, "0");
242    property_error_rate = atoi(value);
243}
244
245/* returns length of buffer after errors */
246static int apply_errors(uint8_t *buffer, int length) {
247    int i;
248    if (!property_error_rate) return length;
249
250    for (i = 0; i < length; i++) {
251        if (rand() % 1000 < property_error_rate) {
252            if (rand() % 2) {
253                // 50% chance of dropping byte
254                length--;
255                memcpy(&buffer[i], &buffer[i+1], length-i);
256                ALOGW("dropped byte %d", i);
257            } else {
258                // 50% chance of corruption
259                buffer[i] = (uint8_t)rand();
260                ALOGW("corrupted byte %d", i);
261            }
262        }
263    }
264    return length;
265}
266
267static struct timeval timeval_remaining(struct timespec timeout) {
268    struct timespec now;
269    struct timeval delta;
270    clock_gettime(CLOCK_MONOTONIC, &now);
271
272    delta.tv_sec = timeout.tv_sec - now.tv_sec;
273    delta.tv_usec = (timeout.tv_nsec - now.tv_nsec) / (long)1000;
274
275    if (delta.tv_usec < 0) {
276        delta.tv_usec += 1000000;
277        delta.tv_sec--;
278    }
279    if (delta.tv_sec < 0) {
280        delta.tv_sec = 0;
281        delta.tv_usec = 0;
282    }
283    return delta;
284}
285
286static int libnfc_firmware_mode = 0;
287
288/*-----------------------------------------------------------------------------
289
290FUNCTION: phDal4Nfc_uart_read
291
292PURPOSE:  Reads nNbBytesToRead bytes and writes them in pBuffer.
293          Returns the number of bytes really read or -1 in case of error.
294
295-----------------------------------------------------------------------------*/
296int phDal4Nfc_uart_read(uint8_t * pBuffer, int nNbBytesToRead)
297{
298    int ret;
299    int numRead = 0;
300    struct timeval tv;
301    struct timeval *ptv;
302    struct timespec timeout;
303    fd_set rfds;
304
305    DAL_ASSERT_STR(gComPortContext.nOpened == 1, "read called but not opened!");
306    DAL_DEBUG("_uart_read() called to read %d bytes", nNbBytesToRead);
307
308    read_property();
309
310    // Read timeout:
311    // FW mode: 10s timeout
312    // 1 byte read: steady-state LLC length read, allowed to block forever
313    // >1 byte read: LLC payload, 100ms timeout (before pn544 re-transmit)
314    if (nNbBytesToRead > 1 && !libnfc_firmware_mode) {
315        clock_gettime(CLOCK_MONOTONIC, &timeout);
316        timeout.tv_nsec += 100000000;
317        if (timeout.tv_nsec > 1000000000) {
318            timeout.tv_sec++;
319            timeout.tv_nsec -= 1000000000;
320        }
321        ptv = &tv;
322    } else if (libnfc_firmware_mode) {
323        clock_gettime(CLOCK_MONOTONIC, &timeout);
324        timeout.tv_sec += 10;
325        ptv = &tv;
326    } else {
327        ptv = NULL;
328    }
329
330    while (numRead < nNbBytesToRead) {
331       FD_ZERO(&rfds);
332       FD_SET(gComPortContext.nHandle, &rfds);
333
334       if (ptv) {
335          tv = timeval_remaining(timeout);
336          ptv = &tv;
337       }
338
339       ret = select(gComPortContext.nHandle + 1, &rfds, NULL, NULL, ptv);
340       if (ret < 0) {
341           DAL_DEBUG("select() errno=%d", errno);
342           if (errno == EINTR || errno == EAGAIN) {
343               continue;
344           }
345           return -1;
346       } else if (ret == 0) {
347           ALOGW("timeout!");
348           break;  // return partial response
349       }
350       ret = read(gComPortContext.nHandle, pBuffer + numRead, nNbBytesToRead - numRead);
351       if (ret > 0) {
352           ret = apply_errors(pBuffer + numRead, ret);
353
354           DAL_DEBUG("read %d bytes", ret);
355           numRead += ret;
356       } else if (ret == 0) {
357           DAL_PRINT("_uart_read() EOF");
358           return 0;
359       } else {
360           DAL_DEBUG("_uart_read() errno=%d", errno);
361           if (errno == EINTR || errno == EAGAIN) {
362               continue;
363           }
364           return -1;
365       }
366    }
367
368    return numRead;
369}
370
371/*-----------------------------------------------------------------------------
372
373FUNCTION: phDal4Nfc_link_write
374
375PURPOSE:  Writes nNbBytesToWrite bytes from pBuffer to the link
376          Returns the number of bytes that have been wrote to the interface or -1 in case of error.
377
378-----------------------------------------------------------------------------*/
379
380int phDal4Nfc_uart_write(uint8_t * pBuffer, int nNbBytesToWrite)
381{
382    int ret;
383    int numWrote = 0;
384
385    DAL_ASSERT_STR(gComPortContext.nOpened == 1, "write called but not opened!");
386    DAL_DEBUG("_uart_write() called to write %d bytes\n", nNbBytesToWrite);
387
388    while (numWrote < nNbBytesToWrite) {
389        ret = write(gComPortContext.nHandle, pBuffer + numWrote, nNbBytesToWrite - numWrote);
390        if (ret > 0) {
391            DAL_DEBUG("wrote %d bytes", ret);
392            numWrote += ret;
393        } else if (ret == 0) {
394            DAL_PRINT("_uart_write() EOF");
395            return -1;
396        } else {
397            DAL_DEBUG("_uart_write() errno=%d", errno);
398            if (errno == EINTR || errno == EAGAIN) {
399                continue;
400            }
401            return -1;
402        }
403    }
404
405    return numWrote;
406}
407
408/*-----------------------------------------------------------------------------
409
410FUNCTION: phDal4Nfc_uart_reset
411
412PURPOSE:  Reset the PN544, using the VEN pin
413
414-----------------------------------------------------------------------------*/
415int phDal4Nfc_uart_reset(long level)
416{
417    static const char NFC_POWER_PATH[] = "/sys/devices/platform/nfc-power/nfc_power";
418    int sz;
419    int fd = -1;
420    int ret = NFCSTATUS_FAILED;
421    char buffer[2];
422
423    DAL_DEBUG("phDal4Nfc_uart_reset, VEN level = %ld", level);
424
425    if (snprintf(buffer, sizeof(buffer), "%u", (unsigned int)level) != 1) {
426        ALOGE("Bad nfc power level (%u)", (unsigned int)level);
427        goto out;
428    }
429
430    fd = open(NFC_POWER_PATH, O_WRONLY);
431    if (fd < 0) {
432        ALOGE("open(%s) for write failed: %s (%d)", NFC_POWER_PATH,
433                strerror(errno), errno);
434        goto out;
435    }
436    sz = write(fd, &buffer, sizeof(buffer) - 1);
437    if (sz < 0) {
438        ALOGE("write(%s) failed: %s (%d)", NFC_POWER_PATH, strerror(errno),
439             errno);
440        goto out;
441    }
442    ret = NFCSTATUS_SUCCESS;
443    if (level == 2) {
444        libnfc_firmware_mode = 1;
445    } else {
446        libnfc_firmware_mode = 0;
447    }
448
449out:
450    if (fd >= 0) {
451        close(fd);
452    }
453    return ret;
454}
455