From 1b11450312674633b09a0c1165387ff7ee9f5499 Mon Sep 17 00:00:00 2001 From: Laurent Latil Date: Fri, 14 Jun 2013 19:00:47 +0200 Subject: [PATCH] Add I2C protocol support for PN532. --- configure.ac | 4 + libnfc/buses/Makefile.am | 7 + libnfc/buses/i2c.c | 212 ++++++++++++++ libnfc/buses/i2c.h | 60 ++++ libnfc/drivers/Makefile.am | 4 + libnfc/drivers/pn532_i2c.c | 560 +++++++++++++++++++++++++++++++++++++ libnfc/drivers/pn532_i2c.h | 41 +++ libnfc/nfc.c | 13 + m4/libnfc_drivers.m4 | 20 +- 9 files changed, 920 insertions(+), 1 deletion(-) create mode 100644 libnfc/buses/i2c.c create mode 100644 libnfc/buses/i2c.h create mode 100644 libnfc/drivers/pn532_i2c.c create mode 100644 libnfc/drivers/pn532_i2c.h diff --git a/configure.ac b/configure.ac index 8101a27..3bc2942 100644 --- a/configure.ac +++ b/configure.ac @@ -46,6 +46,7 @@ AC_HEADER_STDC AC_HEADER_STDBOOL AC_CHECK_HEADERS([fcntl.h limits.h stdio.h stdlib.h stdint.h stddef.h stdbool.h sys/ioctl.h sys/param.h sys/time.h termios.h]) AC_CHECK_HEADERS([linux/spi/spidev.h], [spi_available="yes"]) +AC_CHECK_HEADERS([linux/i2c-dev.h], [i2c_available="yes"]) AC_CHECK_FUNCS([memmove memset select strdup strerror strstr strtol usleep], [AC_DEFINE([_XOPEN_SOURCE], [600], [Enable POSIX extensions if present])]) @@ -122,6 +123,9 @@ AM_CONDITIONAL(UART_ENABLED, [test x"$uart_required" = x"yes"]) # Enable SPI if AM_CONDITIONAL(SPI_ENABLED, [test x"$spi_required" = x"yes"]) +# Enable I2C if +AM_CONDITIONAL(I2C_ENABLED, [test x"$i2c_required" = x"yes"]) + # Documentation (default: no) AC_ARG_ENABLE([doc],AS_HELP_STRING([--enable-doc],[Enable documentation generation.]),[enable_doc=$enableval],[enable_doc="no"]) diff --git a/libnfc/buses/Makefile.am b/libnfc/buses/Makefile.am index dcaeddd..b6adc4b 100644 --- a/libnfc/buses/Makefile.am +++ b/libnfc/buses/Makefile.am @@ -28,3 +28,10 @@ if LIBUSB_ENABLED libnfcbuses_la_LIBADD += @libusb_LIBS@ endif EXTRA_DIST += usbbus.c usbbus.h + +if I2C_ENABLED + libnfcbuses_la_SOURCES += i2c.c i2c.h + libnfcbuses_la_CFLAGS += + libnfcbuses_la_LIBADD += +endif +EXTRA_DIST += i2c.c i2c.h diff --git a/libnfc/buses/i2c.c b/libnfc/buses/i2c.c new file mode 100644 index 0000000..17bf00d --- /dev/null +++ b/libnfc/buses/i2c.c @@ -0,0 +1,212 @@ +/*- + * Free/Libre Near Field Communication (NFC) library + * + * Libnfc historical contributors: + * Copyright (C) 2009 Roel Verdult + * Copyright (C) 2009-2013 Romuald Conty + * Copyright (C) 2010-2012 Romain Tarti?re + * Copyright (C) 2010-2013 Philippe Teuwen + * Copyright (C) 2012-2013 Ludovic Rousseau + * Additional contributors of this file: + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program 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 Lesser General Public License + * along with this program. If not, see + * + */ + +/** + * @file i2c.c + * @brief I2C driver (implemented / tested for Linux only currently) + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif // HAVE_CONFIG_H +#include "i2c.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "nfc-internal.h" + +#define LINUX_I2C_DRIVER_NAME "linux_i2c" + +#define LOG_GROUP NFC_LOG_GROUP_COM +#define LOG_CATEGORY "libnfc.bus.i2c" + +# if defined (__linux__) +const char *i2c_ports_device_radix[] = + { "i2c-", NULL }; +# else +# error "Can't determine I2C devices standard names for your system" +# endif + + +struct i2c_device +{ + int fd; // I2C device file descriptor +}; + +#define I2C_DATA( X ) ((struct i2c_device *) X) + +i2c_device +i2c_open(const char *pcI2C_busName, uint32_t devAddr) +{ + struct i2c_device *id = malloc(sizeof(struct i2c_device)); + + if (id == 0) + return INVALID_I2C_BUS ; + + id->fd = open(pcI2C_busName, O_RDWR | O_NOCTTY | O_NONBLOCK); + if (id->fd == -1) { + perror("Cannot open I2C bus"); + i2c_close(id); + return INVALID_I2C_BUS ; + } + + if (ioctl(id->fd, I2C_SLAVE, devAddr) < 0) { + perror("Cannot select I2C device"); + i2c_close(id); + return INVALID_I2C_ADDRESS ; + } + + return id; +} + +void +i2c_close(const i2c_device id) +{ + if (I2C_DATA(id) ->fd >= 0) { + close(I2C_DATA(id) ->fd); + } + free(id); +} + +/** + * @brief Read a frame from the I2C device and copy data to \a pbtRx + * + * @param timeout Time out for data read (in milliseconds). 0 for not timeout. + * @return 0 on success, otherwise driver error code + */ +int +i2c_read(i2c_device id, uint8_t *pbtRx, const size_t szRx, void *abort_p, + int timeout) +{ + int iAbortFd = abort_p ? *((int *) abort_p) : 0; + + int res; + int done = 0; + + struct timeval start_tv, cur_tv; + long long duration; + + ssize_t recCount = read(I2C_DATA(id) ->fd, pbtRx, szRx); + + if (recCount < 0) { + res = NFC_EIO; + } else { + if (recCount < (ssize_t)szRx) { + res = NFC_EINVARG; + } else { + res = recCount; + } + } + return res; +} + +/** + * @brief Write a frame to I2C device containing \a pbtTx content + * + * @param timeout Time out for data read (in milliseconds). 0 for not timeout. + * @return 0 on success, otherwise a driver error is returned + */ +int +i2c_write(i2c_device id, const uint8_t *pbtTx, const size_t szTx, int timeout) +{ + LOG_HEX(LOG_GROUP, "TX", pbtTx, szTx); + + ssize_t writeCount; + writeCount = write(I2C_DATA(id) ->fd, pbtTx, szTx); + + if ((const ssize_t) szTx == writeCount) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, + "wrote %d bytes successfully.", szTx); + return NFC_SUCCESS; + } else { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, + "Error: wrote only %d bytes (%d expected).", writeCount, (int) szTx); + return NFC_EIO; + } +} + +char ** +i2c_list_ports(void) +{ + char **res = malloc(sizeof(char *)); + if (!res) { + perror("malloc"); + return res; + } + size_t szRes = 1; + + res[0] = NULL; + DIR *dir; + if ((dir = opendir("/dev")) == NULL ) { + perror("opendir error: /dev"); + return res; + } + struct dirent entry; + struct dirent *result; + + while ((readdir_r(dir, &entry, &result) == 0) && (result != NULL )) { + const char **p = i2c_ports_device_radix; + while (*p) { + if (!strncmp(entry.d_name, *p, strlen(*p))) { + char **res2 = realloc(res, (szRes + 1) * sizeof(char *)); + if (!res2) { + perror("malloc"); + goto oom; + } + res = res2; + if (!(res[szRes - 1] = malloc(6 + strlen(entry.d_name)))) { + perror("malloc"); + goto oom; + } + sprintf(res[szRes - 1], "/dev/%s", entry.d_name); + + szRes++; + res[szRes - 1] = NULL; + } + p++; + } + } +oom: + closedir(dir); + + return res; +} + diff --git a/libnfc/buses/i2c.h b/libnfc/buses/i2c.h new file mode 100644 index 0000000..253476c --- /dev/null +++ b/libnfc/buses/i2c.h @@ -0,0 +1,60 @@ +/*- + * Free/Libre Near Field Communication (NFC) library + * + * Libnfc historical contributors: + * Copyright (C) 2009 Roel Verdult + * Copyright (C) 2009-2013 Romuald Conty + * Copyright (C) 2010-2012 Romain Tarti?re + * Copyright (C) 2010-2013 Philippe Teuwen + * Copyright (C) 2012-2013 Ludovic Rousseau + * Additional contributors of this file: + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program 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 Lesser General Public License + * along with this program. If not, see + * + */ + +/** + * @file i2c.h + * @brief I2C driver header + */ + +#ifndef __NFC_BUS_I2C_H__ +# define __NFC_BUS_I2C_H__ + +# include + +# include +# include +# include + +# include +# include + +// extern const struct i2c_driver linux_i2c_driver; + +typedef void *i2c_device; +# define INVALID_I2C_BUS (void*)(~1) +# define INVALID_I2C_ADDRESS (void*)(~2) + +i2c_device i2c_open(const char *pcI2C_busName, uint32_t devAddr); + +void i2c_close(const i2c_device id); + +int i2c_read(i2c_device id, uint8_t *pbtRx, const size_t szRx, void *abort_p, int timeout); + +int i2c_write(i2c_device id, const uint8_t *pbtTx, const size_t szTx, int timeout); + +char ** i2c_list_ports(void); + +#endif // __NFC_BUS_I2C_H__ diff --git a/libnfc/drivers/Makefile.am b/libnfc/drivers/Makefile.am index aaffaa4..1ac65b2 100644 --- a/libnfc/drivers/Makefile.am +++ b/libnfc/drivers/Makefile.am @@ -35,6 +35,10 @@ if DRIVER_PN532_SPI_ENABLED libnfcdrivers_la_SOURCES += pn532_spi.c pn532_spi.h endif +if DRIVER_PN532_I2C_ENABLED +libnfcdrivers_la_SOURCES += pn532_i2c.c pn532_i2c.h +endif + if PCSC_ENABLED libnfcdrivers_la_CFLAGS += @libpcsclite_CFLAGS@ libnfcdrivers_la_LIBADD += @libpcsclite_LIBS@ diff --git a/libnfc/drivers/pn532_i2c.c b/libnfc/drivers/pn532_i2c.c new file mode 100644 index 0000000..49c789f --- /dev/null +++ b/libnfc/drivers/pn532_i2c.c @@ -0,0 +1,560 @@ +/*- + * Free/Libre Near Field Communication (NFC) library + * + * Libnfc historical contributors: + * Copyright (C) 2009 Roel Verdult + * Copyright (C) 2009-2013 Romuald Conty + * Copyright (C) 2010-2012 Romain Tarti?re + * Copyright (C) 2010-2013 Philippe Teuwen + * Copyright (C) 2012-2013 Ludovic Rousseau + * Additional contributors of this file: + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program 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 Lesser General Public License + * along with this program. If not, see + */ + +/** + * @file pn532_i2c.c + * @brief PN532 driver using I2C bus. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif // HAVE_CONFIG_H + +#include "pn532_i2c.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "drivers.h" +#include "nfc-internal.h" +#include "chips/pn53x.h" +#include "chips/pn53x-internal.h" +#include "buses/i2c.h" + +#define PN532_I2C_DRIVER_NAME "pn532_i2c" + +#define LOG_CATEGORY "libnfc.driver.pn532_i2c" +#define LOG_GROUP NFC_LOG_GROUP_DRIVER + +// I2C address of the PN532 chip. +#define PN532_I2C_ADDR 0x24 + +// Internal data structs +const struct pn53x_io pn532_i2c_io; + +struct pn532_i2c_data { + i2c_device dev; + volatile bool abort_flag; +}; + +/* Delay for the loop waiting for READY frame (in ms) */ +#define PN532_RDY_LOOP_DELAY 90 + +const struct timespec rdyDelay = { + .tv_sec = 0, + .tv_nsec = PN532_RDY_LOOP_DELAY * 1000 * 1000 +}; + +/* Private Functions Prototypes */ + +static nfc_device *pn532_i2c_open(const nfc_context *context, const nfc_connstring connstring); + +static void pn532_i2c_close(nfc_device *pnd); + +static int pn532_i2c_send(nfc_device *pnd, const uint8_t *pbtData, const size_t szData, int timeout); + +static int pn532_i2c_ack(nfc_device *pnd); + +static int pn532_i2c_abort_command(nfc_device *pnd); + +static int pn532_i2c_wakeup(nfc_device *pnd); + +static int pn532_i2c_wait_rdyframe(nfc_device *pnd, uint8_t *pbtData, const size_t szDataLen, int timeout); + +static size_t pn532_i2c_scan(const nfc_context *context, nfc_connstring connstrings[], const size_t connstrings_len); + + +#define DRIVER_DATA(pnd) ((struct pn532_i2c_data*)(pnd->driver_data)) + + +static size_t +pn532_i2c_scan(const nfc_context *context, nfc_connstring connstrings[], const size_t connstrings_len) +{ + size_t device_found = 0; + i2c_device id; + char **i2cPorts = i2c_list_ports(); + const char *i2cPort; + int iDevice = 0; + + while ((i2cPort = i2cPorts[iDevice++])) { + id = i2c_open(i2cPort, PN532_I2C_ADDR); + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Trying to find PN532 device on I2C bus %s.", i2cPort); + + if ((id != INVALID_I2C_ADDRESS) && (id != INVALID_I2C_BUS)) { + + nfc_connstring connstring; + snprintf(connstring, sizeof(nfc_connstring), "%s:%s", PN532_I2C_DRIVER_NAME, i2cPort); + nfc_device *pnd = nfc_device_new(context, connstring); + if (!pnd) { + perror("malloc"); + i2c_close(id); + return 0; + } + pnd->driver = &pn532_i2c_driver; + pnd->driver_data = malloc(sizeof(struct pn532_i2c_data)); + if (!pnd->driver_data) { + perror("malloc"); + i2c_close(id); + nfc_device_free(pnd); + return 0; + } + DRIVER_DATA(pnd)->dev = id; + + // Alloc and init chip's data + if (pn53x_data_new(pnd, &pn532_i2c_io) == NULL) { + perror("malloc"); + i2c_close(DRIVER_DATA(pnd)->dev); + nfc_device_free(pnd); + return 0; + } + + // SAMConfiguration command if needed to wakeup the chip and pn53x_SAMConfiguration check if the chip is a PN532 + CHIP_DATA(pnd)->type = PN532; + // This device starts in LowVBat power mode + CHIP_DATA(pnd)->power_mode = LOWVBAT; + + DRIVER_DATA(pnd)->abort_flag = false; + + // Check communication using "Diagnose" command, with "Communication test" (0x00) + int res = pn53x_check_communication(pnd); + i2c_close(DRIVER_DATA(pnd)->dev); + pn53x_data_free(pnd); + nfc_device_free(pnd); + if (res < 0) { + continue; + } + + memcpy(connstrings[device_found], connstring, sizeof(nfc_connstring)); + device_found++; + + // Test if we reach the maximum "wanted" devices + if (device_found >= connstrings_len) + break; + } + } + iDevice = 0; + while ((i2cPort = i2cPorts[iDevice++])) { + free((void *)i2cPort); + } + free(i2cPorts); + return device_found; +} + +static void +pn532_i2c_close(nfc_device *pnd) +{ + pn53x_idle(pnd); + i2c_close(DRIVER_DATA(pnd)->dev); + + pn53x_data_free(pnd); + nfc_device_free(pnd); +} + +static nfc_device * +pn532_i2c_open(const nfc_context *context, const nfc_connstring connstring) +{ + char *i2c_devname; + i2c_device i2c_dev; + nfc_device *pnd; + + // pn532_i2c: + int connstring_decode_level = connstring_decode(connstring, PN532_I2C_DRIVER_NAME, NULL, &i2c_devname, NULL); + + switch(connstring_decode_level) { + case 2: + break; + case 1: + break; + case 0: + return NULL; + } + + i2c_dev = i2c_open(i2c_devname, PN532_I2C_ADDR); + + if (i2c_dev == INVALID_I2C_BUS || i2c_dev == INVALID_I2C_ADDRESS) { + return NULL; + } + + pnd = nfc_device_new(context, connstring); + if (!pnd) { + perror("malloc"); + i2c_close(i2c_dev); + return NULL; + } + snprintf(pnd->name, sizeof(pnd->name), "%s:%s", PN532_I2C_DRIVER_NAME, i2c_devname); + + pnd->driver_data = malloc(sizeof(struct pn532_i2c_data)); + if (!pnd->driver_data) { + perror("malloc"); + i2c_close(i2c_dev); + nfc_device_free(pnd); + return NULL; + } + DRIVER_DATA(pnd)->dev = i2c_dev; + + // Alloc and init chip's data + if (pn53x_data_new(pnd, &pn532_i2c_io) == NULL) { + perror("malloc"); + i2c_close(i2c_dev); + nfc_device_free(pnd); + return NULL; + } + + // SAMConfiguration command if needed to wakeup the chip and pn53x_SAMConfiguration check if the chip is a PN532 + CHIP_DATA(pnd)->type = PN532; + // This device starts in LowVBat mode + CHIP_DATA(pnd)->power_mode = LOWVBAT; + + // empirical tuning + CHIP_DATA(pnd)->timer_correction = 48; + pnd->driver = &pn532_i2c_driver; + + DRIVER_DATA(pnd)->abort_flag = false; + + // Check communication using "Diagnose" command, with "Communication test" (0x00) + if (pn53x_check_communication(pnd) < 0) { + nfc_perror(pnd, "pn53x_check_communication"); + pn532_i2c_close(pnd); + return NULL; + } + + pn53x_init(pnd); + return pnd; +} + +static int +pn532_i2c_wakeup(nfc_device *pnd) +{ + /* No specific. PN532 holds SCL during wakeup time */ + CHIP_DATA(pnd)->power_mode = NORMAL; // PN532 should now be awake + return NFC_SUCCESS; +} + +#define PN532_BUFFER_LEN (PN53x_EXTENDED_FRAME__DATA_MAX_LEN + PN53x_EXTENDED_FRAME__OVERHEAD) +static int +pn532_i2c_send(nfc_device *pnd, const uint8_t *pbtData, const size_t szData, int timeout) +{ + int res = 0; + + // Discard any existing data ? + + switch (CHIP_DATA(pnd)->power_mode) { + case LOWVBAT: { + /** PN532C106 wakeup. */ + if ((res = pn532_i2c_wakeup(pnd)) < 0) { + return res; + } + // According to PN532 application note, C106 appendix: to go out Low Vbat mode and enter in normal mode we need to send a SAMConfiguration command + if ((res = pn532_SAMConfiguration(pnd, PSM_NORMAL, 1000)) < 0) { + return res; + } + } + break; + case POWERDOWN: { + if ((res = pn532_i2c_wakeup(pnd)) < 0) { + return res; + } + } + break; + case NORMAL: + // Nothing to do :) + break; + }; + + uint8_t abtFrame[PN532_BUFFER_LEN] = { 0x00, 0x00, 0xff }; // Every packet must start with "00 00 ff" + size_t szFrame = 0; + + if ((res = pn53x_build_frame(abtFrame, &szFrame, pbtData, szData)) < 0) { + pnd->last_error = res; + return pnd->last_error; + } + + res = i2c_write(DRIVER_DATA(pnd)->dev, abtFrame, szFrame, timeout); + + if (res < 0) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Unable to transmit data. (TX)"); + pnd->last_error = res; + return pnd->last_error; + } + + uint8_t abtRxBuf[6]; + + // Wait for the ACK frame + res = pn532_i2c_wait_rdyframe(pnd, abtRxBuf, 6, timeout); + if (res < 0) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "%s", "Unable to read ACK"); + pnd->last_error = res; + return pnd->last_error; + } + + if (pn53x_check_ack_frame(pnd, abtRxBuf, res) == 0) { + // The PN53x is running the sent command + } else { + return pnd->last_error; + } + return NFC_SUCCESS; +} + +// pbtData pointer on buffer used to store data +// szDataLen Length of buffer +// +// Return: length (in bytes) of the actual data +static int +pn532_i2c_wait_rdyframe(nfc_device *pnd, uint8_t *pbtData, const size_t szDataLen, int timeout) +{ + bool done = false; + int res; + + struct timeval start_tv, cur_tv; + long long duration; + + bool *abort_p = NULL; + + /* Actual response frame includes a RDY byte */ + uint8_t *i2cRx = malloc(szDataLen + 1); + + if (NULL == i2cRx) { + perror("malloc"); + return NFC_ESOFT; + } + + if (timeout > 0) { + gettimeofday(&start_tv, NULL ); + } + + do { + /* Wait a little bit before reading */ + nanosleep(&rdyDelay, (struct timespec *) NULL ); + + int recCount = i2c_read(DRIVER_DATA(pnd)->dev, i2cRx, szDataLen + 1, abort_p, timeout); + + if (DRIVER_DATA(pnd)->abort_flag) { + /* Reset abort flag */ + DRIVER_DATA(pnd)->abort_flag = false; + return NFC_EOPABORTED; + } + + if (recCount <= 0) { + done = true; + res = NFC_EIO; + } else { + const uint8_t rdy = i2cRx[0]; + if (rdy & 1) { + int copyLength; + + done = true; + res = recCount - 1; + copyLength = MIN (res, (int)szDataLen); + memcpy(pbtData, &(i2cRx[1]), copyLength); + } else { + /* Not ready yet. Check for elapsed timeout. */ + + if (timeout > 0) { + gettimeofday(&cur_tv, NULL ); + duration = (cur_tv.tv_sec - start_tv.tv_sec) * 1000000L + + (cur_tv.tv_usec - start_tv.tv_usec); + + if (duration / 1000 > timeout) { + res = NFC_ETIMEOUT; + done = 1; + + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, + "timeout reached with no RDY frame."); + } + } + } + } + } while (!done); + + return res; +} + +static int +pn532_i2c_receive(nfc_device *pnd, uint8_t *pbtData, const size_t szDataLen, int timeout) +{ + uint8_t frameBuf[PN53X_MAX_FRAME_LENGTH]; + int frameLength; + int TFI_idx; + size_t len; + + frameLength = pn532_i2c_wait_rdyframe(pnd, frameBuf, sizeof(frameBuf), timeout); + if (frameLength < 0) { + goto error; + } + + const uint8_t pn53x_preamble[3] = { 0x00, 0x00, 0xff }; + + if (0 != (memcmp(frameBuf, pn53x_preamble, 3))) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Frame preamble+start code mismatch"); + pnd->last_error = NFC_EIO; + goto error; + } + + if ((0x01 == frameBuf[3]) && (0xff == frameBuf[4])) { + uint8_t errorCode = frameBuf[5]; + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Application level error detected (%d)", errorCode); + pnd->last_error = NFC_EIO; + goto error; + } else if ((0xff == frameBuf[3]) && (0xff == frameBuf[4])) { + // Extended frame + len = (frameBuf[5] << 8) + frameBuf[6]; + + // Verify length checksum + if (((frameBuf[5] + frameBuf[6] + frameBuf[7]) % 256) != 0) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Length checksum mismatch"); + pnd->last_error = NFC_EIO; + goto error; + } + TFI_idx = 8; + } else { + // Normal frame + + len = frameBuf[3]; + + // Verify length checksum + if ((uint8_t) (frameBuf[3] + frameBuf[4])) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Length checksum mismatch"); + pnd->last_error = NFC_EIO; + goto error; + } + TFI_idx = 5; + } + + if ((len-2) > szDataLen) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Unable to receive data: buffer too small. (szDataLen: %" PRIuPTR ", len: %" PRIuPTR ")", szDataLen, len); + pnd->last_error = NFC_EIO; + goto error; + } + + uint8_t TFI = frameBuf[TFI_idx]; + if (TFI != 0xD5) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "TFI Mismatch"); + pnd->last_error = NFC_EIO; + goto error; + } + + if (frameBuf[TFI_idx +1] != CHIP_DATA(pnd)->last_command + 1) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Command Code verification failed. (got %d, expected %d)", + frameBuf[TFI_idx +1], CHIP_DATA(pnd)->last_command + 1); + pnd->last_error = NFC_EIO; + goto error; + } + + uint8_t DCS = frameBuf[TFI_idx + len]; + uint8_t btDCS = DCS; + + // Compute data checksum + for (size_t i = 0; i < len; i++) { + btDCS += frameBuf[TFI_idx +i]; + } + + if (btDCS != 0) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Data checksum mismatch (DCS = %02x, btDCS = %d)", DCS, btDCS); + pnd->last_error = NFC_EIO; + goto error; + } + + if (0x00 != frameBuf[TFI_idx + len + 1]) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Frame postamble mismatch (got %d)", frameBuf[frameLength -1]); + pnd->last_error = NFC_EIO; + goto error; + } + + memcpy(pbtData, &frameBuf[TFI_idx + 2], len - 2); + + /* The PN53x command is done and we successfully received the reply */ + return len - 2; +error: + return pnd->last_error; +} + +int +pn532_i2c_ack(nfc_device *pnd) +{ + if (POWERDOWN == CHIP_DATA(pnd)->power_mode) { + int res = 0; + if ((res = pn532_i2c_wakeup(pnd)) < 0) { + return res; + } + } + return i2c_write(DRIVER_DATA(pnd)->dev, pn53x_ack_frame, sizeof(pn53x_ack_frame), 0); +} + +static int +pn532_i2c_abort_command(nfc_device *pnd) +{ + if (pnd) { + DRIVER_DATA(pnd)->abort_flag = true; } + return NFC_SUCCESS; +} + +const struct pn53x_io pn532_i2c_io = { + .send = pn532_i2c_send, + .receive = pn532_i2c_receive, +}; + +const struct nfc_driver pn532_i2c_driver = { + .name = PN532_I2C_DRIVER_NAME, + .scan_type = INTRUSIVE, + .scan = pn532_i2c_scan, + .open = pn532_i2c_open, + .close = pn532_i2c_close, + .strerror = pn53x_strerror, + + .initiator_init = pn53x_initiator_init, + .initiator_init_secure_element = pn532_initiator_init_secure_element, + .initiator_select_passive_target = pn53x_initiator_select_passive_target, + .initiator_poll_target = pn53x_initiator_poll_target, + .initiator_select_dep_target = pn53x_initiator_select_dep_target, + .initiator_deselect_target = pn53x_initiator_deselect_target, + .initiator_transceive_bytes = pn53x_initiator_transceive_bytes, + .initiator_transceive_bits = pn53x_initiator_transceive_bits, + .initiator_transceive_bytes_timed = pn53x_initiator_transceive_bytes_timed, + .initiator_transceive_bits_timed = pn53x_initiator_transceive_bits_timed, + .initiator_target_is_present = pn53x_initiator_target_is_present, + + .target_init = pn53x_target_init, + .target_send_bytes = pn53x_target_send_bytes, + .target_receive_bytes = pn53x_target_receive_bytes, + .target_send_bits = pn53x_target_send_bits, + .target_receive_bits = pn53x_target_receive_bits, + + .device_set_property_bool = pn53x_set_property_bool, + .device_set_property_int = pn53x_set_property_int, + .get_supported_modulation = pn53x_get_supported_modulation, + .get_supported_baud_rate = pn53x_get_supported_baud_rate, + .device_get_information_about = pn53x_get_information_about, + + .abort_command = pn532_i2c_abort_command, + .idle = pn53x_idle, + .powerdown = pn53x_PowerDown, +}; + diff --git a/libnfc/drivers/pn532_i2c.h b/libnfc/drivers/pn532_i2c.h new file mode 100644 index 0000000..cf68349 --- /dev/null +++ b/libnfc/drivers/pn532_i2c.h @@ -0,0 +1,41 @@ +/*- + * Free/Libre Near Field Communication (NFC) library + * + * Libnfc historical contributors: + * Copyright (C) 2009 Roel Verdult + * Copyright (C) 2009-2013 Romuald Conty + * Copyright (C) 2010-2012 Romain Tarti?re + * Copyright (C) 2010-2013 Philippe Teuwen + * Copyright (C) 2012-2013 Ludovic Rousseau + * Additional contributors of this file: + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program 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 Lesser General Public License + * along with this program. If not, see + */ + +/** + * @file pn532_i2c.h + * @brief Driver for PN532 connected through I2C bus + */ + +#ifndef __NFC_DRIVER_PN532_I2C_H__ +#define __NFC_DRIVER_PN532_I2C_H__ + +#include + +# define PN53X_MAX_FRAME_LENGTH 265 + +/* Reference to the I2C driver structure */ +extern const struct nfc_driver pn532_i2c_driver; + +#endif // ! __NFC_DRIVER_I2C_H__ diff --git a/libnfc/nfc.c b/libnfc/nfc.c index aa896b6..47b8a30 100644 --- a/libnfc/nfc.c +++ b/libnfc/nfc.c @@ -113,6 +113,10 @@ # include "drivers/pn532_spi.h" #endif /* DRIVER_PN532_SPI_ENABLED */ +#if defined (DRIVER_PN532_I2C_ENABLED) +# include "drivers/pn532_i2c.h" +#endif /* DRIVER_PN532_I2C_ENABLED */ + #define LOG_CATEGORY "libnfc.general" #define LOG_GROUP NFC_LOG_GROUP_GENERAL @@ -145,11 +149,15 @@ nfc_drivers_init(void) #if defined (DRIVER_PN532_SPI_ENABLED) nfc_register_driver(&pn532_spi_driver); #endif /* DRIVER_PN532_SPI_ENABLED */ +#if defined (DRIVER_PN532_I2C_ENABLED) + nfc_register_driver(&pn532_i2c_driver); +#endif /* DRIVER_PN532_I2C_ENABLED */ #if defined (DRIVER_ARYGON_ENABLED) nfc_register_driver(&arygon_driver); #endif /* DRIVER_ARYGON_ENABLED */ } + /** @ingroup lib * @brief Register an NFC device driver with libnfc. * This function registers a driver with libnfc, the caller is responsible of managing the lifetime of the @@ -189,6 +197,11 @@ nfc_init(nfc_context **context) } if (!nfc_drivers) nfc_drivers_init(); + +//#if defined (I2C_DRIVERS_ENABLED) +// if (!i2c_drivers) +// i2c_drivers_init(); +//#endif } /** @ingroup lib diff --git a/m4/libnfc_drivers.m4 b/m4/libnfc_drivers.m4 index 0a0e47f..46327e9 100644 --- a/m4/libnfc_drivers.m4 +++ b/m4/libnfc_drivers.m4 @@ -4,7 +4,7 @@ AC_DEFUN([LIBNFC_ARG_WITH_DRIVERS], [ AC_MSG_CHECKING(which drivers to build) AC_ARG_WITH(drivers, - AS_HELP_STRING([--with-drivers=DRIVERS], [Use a custom driver set, where DRIVERS is a comma-separated list of drivers to build support for. Available drivers are: 'acr122_pcsc', 'acr122_usb', 'acr122s', 'arygon', 'pn532_spi', 'pn532_uart' and 'pn53x_usb'. Default drivers set is 'acr122_usb,acr122s,arygon,pn532_spi,pn532_uart,pn53x_usb'. The special driver set 'all' compile all available drivers.]), + AS_HELP_STRING([--with-drivers=DRIVERS], [Use a custom driver set, where DRIVERS is a coma-separated list of drivers to build support for. Available drivers are: 'acr122_pcsc', 'acr122_usb', 'acr122s', 'arygon', 'pn532_spi', 'pn532_uart', 'pn532_i2c' and 'pn53x_usb'. Default drivers set is 'acr122_usb,acr122s,arygon,pn532_spi,pn532_i2c,pn532_uart,pn53x_usb'. The special driver set 'all' compile all available drivers.]), [ case "${withval}" in yes | no) dnl ignore calls without any arguments @@ -30,6 +30,10 @@ AC_DEFUN([LIBNFC_ARG_WITH_DRIVERS], then DRIVER_BUILD_LIST="$DRIVER_BUILD_LIST pn532_spi" fi + if test x"$i2c_available" = x"yes" + then + DRIVER_BUILD_LIST="$DRIVER_BUILD_LIST pn532_i2c" + fi ;; all) DRIVER_BUILD_LIST="acr122_pcsc acr122_usb acr122s arygon pn53x_usb pn532_uart" @@ -37,6 +41,10 @@ AC_DEFUN([LIBNFC_ARG_WITH_DRIVERS], then DRIVER_BUILD_LIST="$DRIVER_BUILD_LIST pn532_spi" fi + if test x"$i2c_available" = x"yes" + then + DRIVER_BUILD_LIST="$DRIVER_BUILD_LIST pn532_i2c" + fi ;; esac @@ -49,6 +57,7 @@ AC_DEFUN([LIBNFC_ARG_WITH_DRIVERS], driver_arygon_enabled="no" driver_pn532_uart_enabled="no" driver_pn532_spi_enabled="no" + driver_pn532_i2c_enabled="no" for driver in ${DRIVER_BUILD_LIST} do @@ -88,6 +97,11 @@ AC_DEFUN([LIBNFC_ARG_WITH_DRIVERS], driver_pn532_spi_enabled="yes" DRIVERS_CFLAGS="$DRIVERS_CFLAGS -DDRIVER_PN532_SPI_ENABLED" ;; + pn532_i2c) + i2c_required="yes" + driver_pn532_i2c_enabled="yes" + DRIVERS_CFLAGS="$DRIVERS_CFLAGS -DDRIVER_PN532_I2C_ENABLED -DI2C_DRIVERS_ENABLED" + ;; *) AC_MSG_ERROR([Unknow driver: $driver]) ;; @@ -101,6 +115,9 @@ AC_DEFUN([LIBNFC_ARG_WITH_DRIVERS], AM_CONDITIONAL(DRIVER_ARYGON_ENABLED, [test x"$driver_arygon_enabled" = xyes]) AM_CONDITIONAL(DRIVER_PN532_UART_ENABLED, [test x"$driver_pn532_uart_enabled" = xyes]) AM_CONDITIONAL(DRIVER_PN532_SPI_ENABLED, [test x"$driver_pn532_spi_enabled" = xyes]) + AM_CONDITIONAL(DRIVER_PN532_I2C_ENABLED, [test x"$driver_pn532_i2c_enabled" = xyes]) + + AM_CONDITIONAL(I2C_DRIVERS_ENABLED, [test x"$i2c_required" = xyes]) ]) AC_DEFUN([LIBNFC_DRIVERS_SUMMARY],[ @@ -113,4 +130,5 @@ echo " arygon........... $driver_arygon_enabled" echo " pn53x_usb........ $driver_pn53x_usb_enabled" echo " pn532_uart....... $driver_pn532_uart_enabled" echo " pn532_spi....... $driver_pn532_spi_enabled" +echo " pn532_i2c........ $driver_pn532_i2c_enabled" ])