diff --git a/libnfc/buses/Makefile.am b/libnfc/buses/Makefile.am index 2ce0720..f617e58 100644 --- a/libnfc/buses/Makefile.am +++ b/libnfc/buses/Makefile.am @@ -3,8 +3,8 @@ AM_CPPFLAGS = $(all_includes) $(LIBNFC_CFLAGS) noinst_LTLIBRARIES = libnfcbuses.la -libnfcbuses_la_SOURCES = uart.c uart.h +libnfcbuses_la_SOURCES = uart.c uart.h spi.c spi.h libnfcbuses_la_CFLAGS = -I$(top_srcdir)/libnfc -EXTRA_DIST = uart_posix.c uart_win32.c +EXTRA_DIST = uart_posix.c uart_win32.c spi_posix.c diff --git a/libnfc/buses/spi.c b/libnfc/buses/spi.c new file mode 100644 index 0000000..2318782 --- /dev/null +++ b/libnfc/buses/spi.c @@ -0,0 +1,44 @@ +/*- + * Public platform independent Near Field Communication (NFC) library + * + * Copyright (C) 2013 Evgeny Boger + * Copyright (C) 2009, 2010 Roel Verdult + * Copyright (C) 2009, 2010 Romuald Conty + * + * 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 spi.c + * @brief SPI driver wrapper + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif // HAVE_CONFIG_H + +#include "spi.h" + +#include +#include "nfc-internal.h" + +// Test if we are dealing with unix operating systems +#ifndef _WIN32 +// The POSIX SPI port implementation +# include "spi_posix.c" +#else +// The windows SPI port implementation +# error "Not implemented" +#endif /* _WIN32 */ diff --git a/libnfc/buses/spi.h b/libnfc/buses/spi.h new file mode 100644 index 0000000..1a15850 --- /dev/null +++ b/libnfc/buses/spi.h @@ -0,0 +1,60 @@ +/*- + * Public platform independent Near Field Communication (NFC) library + * + * Copyright (C) 2013 Evgeny Boger + * Copyright (C) 2009, 2010 Roel Verdult + * Copyright (C) 2010, 2011 Romain Tarti?re + * Copyright (C) 2009, 2010, 2011 Romuald Conty + * + * 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 spi.h + * @brief SPI driver header + */ + +#ifndef __NFC_BUS_SPI_H__ +# define __NFC_BUS_SPI_H__ + +# include + +# include +# include +# include + +# include + +# include + +// Define shortcut to types to make code more readable +typedef void *spi_port; +# define INVALID_SPI_PORT (void*)(~1) +# define CLAIMED_SPI_PORT (void*)(~2) + +spi_port spi_open(const char *pcPortName); +void spi_close(const spi_port sp); + +void spi_set_speed(spi_port sp, const uint32_t uiPortSpeed); +void spi_set_mode(spi_port sp, const uint32_t uiPortMode); +uint32_t spi_get_speed(const spi_port sp); + +int spi_receive(spi_port sp, uint8_t *pbtRx, const size_t szRx, bool lsb_first); +int spi_send(spi_port sp, const uint8_t *pbtTx, const size_t szTx, bool lsb_first); +int spi_send_receive(spi_port sp, const uint8_t *pbtTx, const size_t szTx, uint8_t *pbtRx, const size_t szRx, bool lsb_first); + +char **spi_list_ports(void); + +#endif // __NFC_BUS_SPI_H__ diff --git a/libnfc/buses/spi_posix.c b/libnfc/buses/spi_posix.c new file mode 100644 index 0000000..4c73b68 --- /dev/null +++ b/libnfc/buses/spi_posix.c @@ -0,0 +1,300 @@ +/*- + * Public platform independent Near Field Communication (NFC) library + * + * Copyright (C) 2013 Evgeny Boger + * Copyright (C) 2009, 2010, 2011 Roel Verdult + * Copyright (C) 2009, 2010, 2011, 2012 Romuald Conty + * Copyright (C) 2011 Romain Tarti?re + * + * 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 spi_posix.c + * @brief POSIX SPI driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nfc-internal.h" + +#define LOG_GROUP NFC_LOG_GROUP_COM +#define LOG_CATEGORY "libnfc.bus.spi" + +# if defined(__APPLE__) +const char *spi_ports_device_radix[] = { "spidev", NULL }; +# elif defined (__FreeBSD__) || defined (__OpenBSD__) +const char *spi_ports_device_radix[] = { "spidev", NULL }; +# elif defined (__linux__) +const char *spi_ports_device_radix[] = { "spidev", NULL }; +# else +# error "Can't determine spi port string for your system" +# endif + + +struct spi_port_unix { + int fd; // Serial port file descriptor + //~ struct termios termios_backup; // Terminal info before using the port + //~ struct termios termios_new; // Terminal info during the transaction +}; + +#define SPI_DATA( X ) ((struct spi_port_unix *) X) + + +spi_port +spi_open(const char *pcPortName) +{ + struct spi_port_unix *sp = malloc(sizeof(struct spi_port_unix)); + + if (sp == 0) + return INVALID_SPI_PORT; + + sp->fd = open(pcPortName, O_RDWR | O_NOCTTY | O_NONBLOCK); + if (sp->fd == -1) { + spi_close(sp); + return INVALID_SPI_PORT; + } + + + return sp; +} + + + +void +spi_set_speed(spi_port sp, const uint32_t uiPortSpeed) +{ + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "SPI port speed requested to be set to %d Hz.", uiPortSpeed); + int ret; + ret = ioctl(SPI_DATA(sp)->fd, SPI_IOC_WR_MAX_SPEED_HZ, &uiPortSpeed); + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "ret %d", ret); + + if (ret == -1) log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Error setting SPI speed."); + +} + +void +spi_set_mode(spi_port sp, const uint32_t uiPortMode) +{ + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "SPI port mode requested to be set to %d.", uiPortMode); + int ret; + ret = ioctl(SPI_DATA(sp)->fd, SPI_IOC_WR_MODE, &uiPortMode); + + if (ret == -1) log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Error setting SPI mode."); + +} + +uint32_t +spi_get_speed(spi_port sp) +{ + uint32_t uiPortSpeed = 0; + + int ret; + ret = ioctl(SPI_DATA(sp)->fd, SPI_IOC_RD_MAX_SPEED_HZ, &uiPortSpeed); + + if (ret == -1) log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Error reading SPI speed."); + + return uiPortSpeed; +} + + +void +spi_close(const spi_port sp) +{ + close(SPI_DATA(sp)->fd); + free(sp); +} + + +/** + * @brief Perform bit reversal on one byte \a x + * + * @return reversed byte + */ + +uint8_t +bit_reversal(const uint8_t x) +{ + uint8_t ret = x; + ret = (((ret & 0xaa) >> 1) | ((ret & 0x55) << 1)); + ret = (((ret & 0xcc) >> 2) | ((ret & 0x33) << 2)); + ret = (((ret & 0xf0) >> 4) | ((ret & 0x0f) << 4)); + return ret; +} + + + + + +/** + * @brief Send \a pbtTx content to SPI then receive data from SPI and copy data to \a pbtRx. CS line stays active between transfers as well as during transfers. + * + * @return 0 on success, otherwise a driver error is returned + */ +int +spi_send_receive(spi_port sp, const uint8_t *pbtTx, const size_t szTx, uint8_t *pbtRx, const size_t szRx, bool lsb_first) +{ + size_t transfers = 0; + struct spi_ioc_transfer tr[2]; + + + uint8_t *pbtTxLSB = 0; + + if (szTx) { + LOG_HEX(LOG_GROUP, "TX", pbtTx, szTx); + if (lsb_first) { + pbtTxLSB = malloc(szTx * sizeof(uint8_t)); + if (!pbtTxLSB) { + return NFC_ESOFT; + } + + size_t i; + for (i = 0; i < szTx; ++i) { + pbtTxLSB[i] = bit_reversal(pbtTx[i]); + } + + pbtTx = pbtTxLSB; + } + + struct spi_ioc_transfer tr_send = { .tx_buf = (unsigned long) pbtTx, + .rx_buf = 0, + .len = szTx , + .delay_usecs = 0, + .speed_hz = 0, + .bits_per_word = 0, + }; + tr[transfers] = tr_send; + + ++transfers; + } + + if (szRx) { + struct spi_ioc_transfer tr_receive = { .tx_buf = 0, + .rx_buf = (unsigned long) pbtRx, + .len = szRx, + .delay_usecs = 0, + .speed_hz = 0, + .bits_per_word = 0, + }; + tr[transfers] = tr_receive; + ++transfers; + } + + + + if (transfers) { + int ret = ioctl(SPI_DATA(sp)->fd, SPI_IOC_MESSAGE(transfers), tr); + if (szTx && lsb_first) { + free(pbtTxLSB); + } + + if (ret != (int) (szRx + szTx)) { + return NFC_EIO; + } + + // Reverse received bytes if needed + if (szRx) { + if (lsb_first) { + size_t i; + for (i = 0; i < szRx; ++i) { + pbtRx[i] = bit_reversal(pbtRx[i]); + } + } + + LOG_HEX(LOG_GROUP, "RX", pbtRx, szRx); + } + } + + + return NFC_SUCCESS; +} + + +/** + * @brief Receive data from SPI and copy data to \a pbtRx + * + * @return 0 on success, otherwise driver error code + */ +int +spi_receive(spi_port sp, uint8_t *pbtRx, const size_t szRx, bool lsb_first) +{ + return spi_send_receive(sp, 0, 0, pbtRx, szRx, lsb_first); +} + + +/** + * @brief Send \a pbtTx content to SPI + * + * @return 0 on success, otherwise a driver error is returned + */ +int +spi_send(spi_port sp, const uint8_t *pbtTx, const size_t szTx, bool lsb_first) +{ + return spi_send_receive(sp, pbtTx, szTx, 0, 0, lsb_first); +} + + +char ** +spi_list_ports(void) +{ + char **res = malloc(sizeof(char *)); + size_t szRes = 1; + + res[0] = NULL; + + DIR *pdDir = opendir("/dev"); + struct dirent *pdDirEnt; + while ((pdDirEnt = readdir(pdDir)) != NULL) { +#if !defined(__APPLE__) + if (!isdigit(pdDirEnt->d_name[strlen(pdDirEnt->d_name) - 1])) + continue; +#endif + const char **p = spi_ports_device_radix; + while (*p) { + if (!strncmp(pdDirEnt->d_name, *p, strlen(*p))) { + char **res2 = realloc(res, (szRes + 1) * sizeof(char *)); + if (!res2) + goto oom; + + res = res2; + if (!(res[szRes - 1] = malloc(6 + strlen(pdDirEnt->d_name)))) + goto oom; + + sprintf(res[szRes - 1], "/dev/%s", pdDirEnt->d_name); + + szRes++; + res[szRes - 1] = NULL; + } + p++; + } + } +oom: + closedir(pdDir); + + return res; +} diff --git a/libnfc/drivers/Makefile.am b/libnfc/drivers/Makefile.am index 1d24924..aaffaa4 100644 --- a/libnfc/drivers/Makefile.am +++ b/libnfc/drivers/Makefile.am @@ -3,9 +3,9 @@ AM_CPPFLAGS = $(all_includes) $(LIBNFC_CFLAGS) noinst_LTLIBRARIES = libnfcdrivers.la -libnfcdrivers_la_SOURCES = +libnfcdrivers_la_SOURCES = libnfcdrivers_la_CFLAGS = @DRIVERS_CFLAGS@ -I$(top_srcdir)/libnfc -I$(top_srcdir)/libnfc/buses -libnfcdrivers_la_LIBADD = +libnfcdrivers_la_LIBADD = if DRIVER_ACR122_PCSC_ENABLED libnfcdrivers_la_SOURCES += acr122_pcsc.c acr122_pcsc.h @@ -31,6 +31,10 @@ if DRIVER_PN532_UART_ENABLED libnfcdrivers_la_SOURCES += pn532_uart.c pn532_uart.h endif +if DRIVER_PN532_SPI_ENABLED +libnfcdrivers_la_SOURCES += pn532_spi.c pn532_spi.h +endif + if PCSC_ENABLED libnfcdrivers_la_CFLAGS += @libpcsclite_CFLAGS@ libnfcdrivers_la_LIBADD += @libpcsclite_LIBS@ diff --git a/libnfc/drivers/pn532_spi.c b/libnfc/drivers/pn532_spi.c new file mode 100644 index 0000000..dcf247b --- /dev/null +++ b/libnfc/drivers/pn532_spi.c @@ -0,0 +1,676 @@ +/*- + * Public platform independent Near Field Communication (NFC) library + * + * Copyright (C) 2013 Evgeny Boger + * Copyright (C) 2010 Roel Verdult + * Copyright (C) 2011 Romain Tarti?re + * Copyright (C) 2010, 2011, 2012 Romuald Conty + * + * 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_spi.c + * @brief PN532 driver using SPI bus + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif // HAVE_CONFIG_H + +#include "pn532_spi.h" + +#include +#include +#include +#include + +#include + +#include "drivers.h" +#include "nfc-internal.h" +#include "chips/pn53x.h" +#include "chips/pn53x-internal.h" +#include "spi.h" + +#define PN532_SPI_DEFAULT_SPEED 1000000 // 1 MHz +#define PN532_SPI_DRIVER_NAME "pn532_spi" +#define PN532_SPI_MODE SPI_MODE_0 + +#define LOG_CATEGORY "libnfc.driver.pn532_spi" +#define LOG_GROUP NFC_LOG_GROUP_DRIVER + +// Internal data structs +const struct pn53x_io pn532_spi_io; +struct pn532_spi_data { + spi_port port; + volatile bool abort_flag; +}; + +static const uint8_t pn532_spi_cmd_dataread = 0x03; +static const uint8_t pn532_spi_cmd_datawrite = 0x01; + + +// Prototypes +int pn532_spi_ack(nfc_device *pnd); +int pn532_spi_wakeup(nfc_device *pnd); + +#define DRIVER_DATA(pnd) ((struct pn532_spi_data*)(pnd->driver_data)) + +static size_t +pn532_spi_scan(const nfc_context *context, nfc_connstring connstrings[], const size_t connstrings_len) +{ + size_t device_found = 0; + spi_port sp; + char **acPorts = spi_list_ports(); + const char *acPort; + int iDevice = 0; + + while ((acPort = acPorts[iDevice++])) { + sp = spi_open(acPort); + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Trying to find PN532 device on SPI port: %s at %d Hz.", acPort, PN532_SPI_DEFAULT_SPEED); + + if ((sp != INVALID_SPI_PORT) && (sp != CLAIMED_SPI_PORT)) { + // We need to flush input to be sure first reply does not comes from older byte transceive + //~ spi_flush_input(sp); + // Serial port claimed but we need to check if a PN532_SPI is opened. + spi_set_speed(sp, PN532_SPI_DEFAULT_SPEED); + spi_set_mode(sp, PN532_SPI_MODE); + + nfc_connstring connstring; + snprintf(connstring, sizeof(nfc_connstring), "%s:%s:%"PRIu32, PN532_SPI_DRIVER_NAME, acPort, PN532_SPI_DEFAULT_SPEED); + nfc_device *pnd = nfc_device_new(context, connstring); + pnd->driver = &pn532_spi_driver; + pnd->driver_data = malloc(sizeof(struct pn532_spi_data)); + DRIVER_DATA(pnd)->port = sp; + + // Alloc and init chip's data + pn53x_data_new(pnd, &pn532_spi_io); + // 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); + pn53x_data_free(pnd); + nfc_device_free(pnd); + spi_close(sp); + 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 ((acPort = acPorts[iDevice++])) { + free((void *)acPort); + } + free(acPorts); + return device_found; +} + +struct pn532_spi_descriptor { + char port[128]; + uint32_t speed; +}; + +static int +pn532_connstring_decode(const nfc_connstring connstring, struct pn532_spi_descriptor *desc) +{ + char *cs = malloc(strlen(connstring) + 1); + if (!cs) { + perror("malloc"); + return -1; + } + strcpy(cs, connstring); + const char *driver_name = strtok(cs, ":"); + if (!driver_name) { + // Parse error + free(cs); + return -1; + } + + if (0 != strcmp(driver_name, PN532_SPI_DRIVER_NAME)) { + // Driver name does not match. + free(cs); + return 0; + } + + const char *port = strtok(NULL, ":"); + if (!port) { + // Only driver name was specified (or parsing error) + free(cs); + return 1; + } + strncpy(desc->port, port, sizeof(desc->port) - 1); + desc->port[sizeof(desc->port) - 1] = '\0'; + + const char *speed_s = strtok(NULL, ":"); + if (!speed_s) { + // speed not specified (or parsing error) + free(cs); + return 2; + } + unsigned long speed; + if (sscanf(speed_s, "%lu", &speed) != 1) { + // speed_s is not a number + free(cs); + return 2; + } + desc->speed = speed; + + free(cs); + return 3; +} + +static void +pn532_spi_close(nfc_device *pnd) +{ + pn53x_idle(pnd); + + // Release SPI port + spi_close(DRIVER_DATA(pnd)->port); + + pn53x_data_free(pnd); + nfc_device_free(pnd); +} + +static nfc_device * +pn532_spi_open(const nfc_context *context, const nfc_connstring connstring) +{ + struct pn532_spi_descriptor ndd; + int connstring_decode_level = pn532_connstring_decode(connstring, &ndd); + + if (connstring_decode_level < 2) { + return NULL; + } + if (connstring_decode_level < 3) { + ndd.speed = PN532_SPI_DEFAULT_SPEED; + } + spi_port sp; + nfc_device *pnd = NULL; + + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Attempt to open: %s at %d Hz.", ndd.port, ndd.speed); + sp = spi_open(ndd.port); + + if (sp == INVALID_SPI_PORT) + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Invalid SPI port: %s", ndd.port); + if (sp == CLAIMED_SPI_PORT) + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "SPI port already claimed: %s", ndd.port); + if ((sp == CLAIMED_SPI_PORT) || (sp == INVALID_SPI_PORT)) + return NULL; + + spi_set_speed(sp, ndd.speed); + spi_set_mode(sp, PN532_SPI_MODE); + + // We have a connection + pnd = nfc_device_new(context, connstring); + snprintf(pnd->name, sizeof(pnd->name), "%s:%s", PN532_SPI_DRIVER_NAME, ndd.port); + + pnd->driver_data = malloc(sizeof(struct pn532_spi_data)); + DRIVER_DATA(pnd)->port = sp; + + // Alloc and init chip's data + pn53x_data_new(pnd, &pn532_spi_io); + // 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_spi_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_spi_close(pnd); + return NULL; + } + + pn53x_init(pnd); + return pnd; +} + +static int +pn532_spi_read_spi_status(nfc_device *pnd, int timeout) +{ + static const uint8_t pn532_spi_statread_cmd = 0x02; + + uint8_t spi_status = 0; + int res = spi_send_receive(DRIVER_DATA(pnd)->port, &pn532_spi_statread_cmd, 1, &spi_status, 1, true); + + + if (res != NFC_SUCCESS) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "%s", "Unable to read SPI status"); + return res; + } + + return spi_status; +} + +int +pn532_spi_wakeup(nfc_device *pnd) +{ + /* SPI wakeup is basically activating chipselect for several ms. + * To do so, we are sending harmless command at very low speed */ + + int res; + const uint32_t prev_port_speed = spi_get_speed(DRIVER_DATA(pnd)->port); + + + + + // Try to get byte from the SPI line. If PN532 is powered down, the byte will be 0xff (MISO line is high) + uint8_t spi_byte = 0; + res = spi_receive(DRIVER_DATA(pnd)->port, &spi_byte, 1, true); + if (res != NFC_SUCCESS) { + return res; + } + + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Got %x byte from SPI line before wakeup", spi_byte); + + CHIP_DATA(pnd)->power_mode = NORMAL; // PN532 will be awake soon + + if(spi_byte == 0xff) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "%s", "Wakeup is needed"); + spi_set_speed(DRIVER_DATA(pnd)->port, 5000); // set slow speed + + res = pn532_SAMConfiguration(pnd, PSM_NORMAL, 1000); // wakeup by sending SAMConfiguration, which works just fine + + spi_set_speed(DRIVER_DATA(pnd)->port, prev_port_speed); + } + + + return res; +} + +#define PN532_BUFFER_LEN (PN53x_EXTENDED_FRAME__DATA_MAX_LEN + PN53x_EXTENDED_FRAME__OVERHEAD) + + +static int +pn532_spi_wait_for_data(nfc_device *pnd, int timeout) +{ + static const uint8_t pn532_spi_ready = 0x01; + static const int pn532_spi_poll_interval = 10; //ms + + + int timer = 0; + + int ret; + while ( (ret = pn532_spi_read_spi_status(pnd, timeout)) != pn532_spi_ready) { + if (ret < 0) { + return ret; + } + + if (DRIVER_DATA(pnd)->abort_flag) { + DRIVER_DATA(pnd)->abort_flag = false; + return NFC_EOPABORTED; + } + + if (timeout > 0) { + timer += pn532_spi_poll_interval; + if (timer > timeout) { + return NFC_ETIMEOUT; + } + + usleep(pn532_spi_poll_interval * 1000); + } + } + + return NFC_SUCCESS; +} + + +static int +pn532_spi_receive_next_chunk(nfc_device *pnd, uint8_t *pbtData, const size_t szDataLen) +{ + // According to datasheet, the entire read operation should be done at once + // However, it seems impossible to do since the length of the frame is stored in the frame + // itself and it's impossible to manualy set CS to low between two read operations + + // It's possible to read the response frame in a series of read operations, provided + // each read operation is preceded by SPI_DATAREAD byte from the host. + + // Unfortunately, the PN532 sends first byte of the second and successive response chunks + // at the same time as host sends SPI_DATAREAD byte + + // Many hardware SPI implementations are half-duplex, so it's became impossible to read this + // first response byte + + // The following hack is used here: we first try to recieve data from PN532 without SPI_DATAREAD + // and then begin full-featured read operation + + // The PN532 do not shift the internal register on the recieve operation, which allows us to read the whole response + + // The example transfer log is as follows: + // CS ..._/---\___________________________/---\________/------\_____________/-----\_________/---\____________/---... + // MOSI (host=>pn532) ... 0x03 0x00 0x00 0x00 0x00 0x00 0x03 0x00 0x00 0x03 0x00 + // MISO (pn532<=host) ... 0x01 0x00 0xff 0x02 0xfe 0xd5 0xd5 0x15 0x16 0x16 0x00 + // linux send/recieve s r r r r r s r r s r + // |<-- data -->| |<-data->| |<-data->| |<-data->| |<-data->| + // |<-- first chunk -->| |<-- second chunk -->| |<-- third chunk -->| + + // The response frame is 0x00 0xff 0x02 0xfe 0xd5 0x15 0x16 0x00 + + + int res = spi_receive(DRIVER_DATA(pnd)->port, pbtData, 1, true); + + if (res != NFC_SUCCESS) { + return res; + } + + res = spi_send_receive(DRIVER_DATA(pnd)->port, &pn532_spi_cmd_dataread, 1, pbtData + 1, szDataLen - 1, true); + + return res; +} + +static int +pn532_spi_receive(nfc_device *pnd, uint8_t *pbtData, const size_t szDataLen, int timeout) +{ + uint8_t abtRxBuf[5]; + size_t len; + + pnd->last_error = pn532_spi_wait_for_data(pnd, timeout); + + if (NFC_EOPABORTED == pnd->last_error) { + return pn532_spi_ack(pnd); + } + + if (pnd->last_error != NFC_SUCCESS) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Unable to wait for SPI data. (RX)"); + goto error; + } + + pnd->last_error = spi_send_receive(DRIVER_DATA(pnd)->port, &pn532_spi_cmd_dataread, 1, abtRxBuf , 4, true); + + if (pnd->last_error < 0) { + goto error; + } + + const uint8_t pn53x_long_preamble[3] = { 0x00, 0x00, 0xff }; + if (0 == (memcmp(abtRxBuf, pn53x_long_preamble, 3))) { + // long preamble + + // omit first byte + for (size_t i = 0; i < 3; ++i){ + abtRxBuf[i] = abtRxBuf[i + 1]; + } + + // need one more byte + pnd->last_error = pn532_spi_receive_next_chunk(pnd, abtRxBuf + 3, 1); + if (pnd->last_error != 0) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Unable to receive one more byte for long preamble frame. (RX)"); + goto error; + } + } + + + const uint8_t pn53x_preamble[2] = { 0x00, 0xff }; + if (0 != (memcmp(abtRxBuf, pn53x_preamble, 2))) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", " preamble+start code mismatch"); + pnd->last_error = NFC_EIO; + goto error; + } + + if ((0x01 == abtRxBuf[2]) && (0xff == abtRxBuf[3])) { + // Error frame + pn532_spi_receive_next_chunk(pnd, abtRxBuf, 3); + + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Application level error detected"); + pnd->last_error = NFC_EIO; + goto error; + } else if ((0xff == abtRxBuf[2]) && (0xff == abtRxBuf[3])) { + // Extended frame + pnd->last_error = pn532_spi_receive_next_chunk(pnd, abtRxBuf, 3); + + if (pnd->last_error != 0) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Unable to receive data. (RX)"); + goto error; + } + // (abtRxBuf[0] << 8) + abtRxBuf[1] (LEN) include TFI + (CC+1) + len = (abtRxBuf[0] << 8) + abtRxBuf[1] - 2; + if (((abtRxBuf[0] + abtRxBuf[1] + abtRxBuf[2]) % 256) != 0) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Length checksum mismatch"); + pnd->last_error = NFC_EIO; + goto error; + } + } else { + // Normal frame + if (256 != (abtRxBuf[2] + abtRxBuf[3])) { + // TODO: Retry + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Length checksum mismatch"); + pnd->last_error = NFC_EIO; + goto error; + } + + // abtRxBuf[3] (LEN) include TFI + (CC+1) + len = abtRxBuf[2] - 2; + } + + if (len > szDataLen) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Unable to receive data: buffer too small. (szDataLen: %zu, len: %zu)", szDataLen, len); + pnd->last_error = NFC_EIO; + goto error; + } + + // TFI + PD0 (CC+1) + + pnd->last_error = pn532_spi_receive_next_chunk(pnd, abtRxBuf, 2); + + if (pnd->last_error != 0) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Unable to receive data. (RX)"); + goto error; + } + + if (abtRxBuf[0] != 0xD5) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "TFI Mismatch"); + pnd->last_error = NFC_EIO; + goto error; + } + + if (abtRxBuf[1] != CHIP_DATA(pnd)->last_command + 1) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Command Code verification failed"); + pnd->last_error = NFC_EIO; + goto error; + } + + if (len) { + pnd->last_error = pn532_spi_receive_next_chunk(pnd, pbtData, len); + + if (pnd->last_error != 0) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Unable to receive data. (RX)"); + goto error; + } + } + + pnd->last_error = pn532_spi_receive_next_chunk(pnd, abtRxBuf, 2); + + if (pnd->last_error != 0) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Unable to receive data. (RX)"); + goto error; + } + + uint8_t btDCS = (256 - 0xD5); + btDCS -= CHIP_DATA(pnd)->last_command + 1; + for (size_t szPos = 0; szPos < len; szPos++) { + btDCS -= pbtData[szPos]; + } + + if (btDCS != abtRxBuf[0]) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Data checksum mismatch"); + pnd->last_error = NFC_EIO; + goto error; + } + + if (0x00 != abtRxBuf[1]) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Frame postamble mismatch"); + pnd->last_error = NFC_EIO; + goto error; + } + // The PN53x command is done and we successfully received the reply + return len; +error: + return pnd->last_error; +} + +static int +pn532_spi_send(nfc_device *pnd, const uint8_t *pbtData, const size_t szData, int timeout) +{ + int res = 0; + // Before sending anything, we need to discard from any junk bytes + //~ spi_flush_input(DRIVER_DATA(pnd)->port); + + switch (CHIP_DATA(pnd)->power_mode) { + case LOWVBAT: { + /** PN532C106 wakeup. */ + if ((res = pn532_spi_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_spi_wakeup(pnd)) < 0) { + return res; + } + } + break; + case NORMAL: + // Nothing to do :) + break; + }; + + uint8_t abtFrame[PN532_BUFFER_LEN + 1] = { pn532_spi_cmd_datawrite, 0x00, 0x00, 0xff }; // SPI data transfer starts with DATAWRITE (0x01) byte, Every packet must start with "00 00 ff" + size_t szFrame = 0; + + if ((res = pn53x_build_frame(abtFrame + 1, &szFrame, pbtData, szData)) < 0) { + pnd->last_error = res; + return pnd->last_error; + } + + res = spi_send(DRIVER_DATA(pnd)->port, abtFrame, szFrame, true); + 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; + } + + res = pn532_spi_wait_for_data(pnd, timeout); + if (res != NFC_SUCCESS) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Unable to wait for SPI data. (RX)"); + pnd->last_error = res; + return pnd->last_error; + } + + + + uint8_t abtRxBuf[6]; + res = spi_send_receive(DRIVER_DATA(pnd)->port, &pn532_spi_cmd_dataread, 1, abtRxBuf, 6, true); + + 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, sizeof(abtRxBuf)) == 0) { + // The PN53x is running the sent command + } else { + return pnd->last_error; + } + return NFC_SUCCESS; +} + + +int +pn532_spi_ack(nfc_device *pnd) +{ + const size_t ack_frame_len = (sizeof(pn53x_ack_frame) / sizeof(pn53x_ack_frame[0])); + uint8_t ack_tx_buf [1 + ack_frame_len]; + + ack_tx_buf[0] = pn532_spi_cmd_datawrite; + memcpy(ack_tx_buf + 1, pn53x_ack_frame, ack_frame_len); + + + int res = spi_send(DRIVER_DATA(pnd)->port, ack_tx_buf, ack_frame_len + 1, true); + return res; +} + +static int +pn532_spi_abort_command(nfc_device *pnd) +{ + if (pnd) { + DRIVER_DATA(pnd)->abort_flag = true; + } + + return NFC_SUCCESS; +} + +const struct pn53x_io pn532_spi_io = { + .send = pn532_spi_send, + .receive = pn532_spi_receive, +}; + +const struct nfc_driver pn532_spi_driver = { + .name = PN532_SPI_DRIVER_NAME, + .scan_type = INTRUSIVE, + .scan = pn532_spi_scan, + .open = pn532_spi_open, + .close = pn532_spi_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_spi_abort_command, + .idle = pn53x_idle, + .powerdown = pn53x_PowerDown, +}; + diff --git a/libnfc/drivers/pn532_spi.h b/libnfc/drivers/pn532_spi.h new file mode 100644 index 0000000..3fefd89 --- /dev/null +++ b/libnfc/drivers/pn532_spi.h @@ -0,0 +1,35 @@ +/*- + * Public platform independent Near Field Communication (NFC) library + * + * Copyright (C) 2013 Evgeny Boger + * Copyright (C) 2010 Roel Verdult + * Copyright (C) 2011 Romain Tarti?re + * Copyright (C) 2010, 2011, 2012 Romuald Conty + * + * 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_spi.h + * @brief Driver for PN532 connected in SPI + */ + +#ifndef __NFC_DRIVER_PN532_SPI_H__ +#define __NFC_DRIVER_PN532_SPI_H__ + +#include + +extern const struct nfc_driver pn532_spi_driver; + +#endif // ! __NFC_DRIVER_PN532_SPI_H__ diff --git a/libnfc/nfc.c b/libnfc/nfc.c index b3ce252..f0d8b50 100644 --- a/libnfc/nfc.c +++ b/libnfc/nfc.c @@ -105,6 +105,10 @@ # include "drivers/pn532_uart.h" #endif /* DRIVER_PN532_UART_ENABLED */ +#if defined (DRIVER_PN532_SPI_ENABLED) +# include "drivers/pn532_spi.h" +#endif /* DRIVER_PN532_SPI_ENABLED */ + #define LOG_CATEGORY "libnfc.general" #define LOG_GROUP NFC_LOG_GROUP_GENERAL @@ -134,6 +138,9 @@ nfc_drivers_init() #if defined (DRIVER_PN532_UART_ENABLED) nfc_register_driver(&pn532_uart_driver); #endif /* DRIVER_PN532_UART_ENABLED */ +#if defined (DRIVER_PN532_SPI_ENABLED) + nfc_register_driver(&pn532_spi_driver); +#endif /* DRIVER_PN532_SPI_ENABLED */ #if defined (DRIVER_ARYGON_ENABLED) nfc_register_driver(&arygon_driver); #endif /* DRIVER_ARYGON_ENABLED */ diff --git a/m4/libnfc_drivers.m4 b/m4/libnfc_drivers.m4 index a0ed071..17a412d 100644 --- a/m4/libnfc_drivers.m4 +++ b/m4/libnfc_drivers.m4 @@ -22,16 +22,16 @@ AC_DEFUN([LIBNFC_ARG_WITH_DRIVERS], AC_MSG_RESULT(default drivers) ] ) - + case "${DRIVER_BUILD_LIST}" in default) - DRIVER_BUILD_LIST="acr122_usb acr122s arygon pn53x_usb pn532_uart" + DRIVER_BUILD_LIST="acr122_usb acr122s arygon pn53x_usb pn532_uart pn532_spi" ;; all) - DRIVER_BUILD_LIST="acr122_pcsc acr122_usb acr122s arygon pn53x_usb pn532_uart" + DRIVER_BUILD_LIST="acr122_pcsc acr122_usb acr122s arygon pn53x_usb pn532_uart pn532_spi" ;; esac - + DRIVERS_CFLAGS="" driver_acr122_pcsc_enabled="no" @@ -40,6 +40,7 @@ AC_DEFUN([LIBNFC_ARG_WITH_DRIVERS], driver_pn53x_usb_enabled="no" driver_arygon_enabled="no" driver_pn532_uart_enabled="no" + driver_pn532_spi_enabled="no" for driver in ${DRIVER_BUILD_LIST} do @@ -71,6 +72,10 @@ AC_DEFUN([LIBNFC_ARG_WITH_DRIVERS], driver_pn532_uart_enabled="yes" DRIVERS_CFLAGS="$DRIVERS_CFLAGS -DDRIVER_PN532_UART_ENABLED" ;; + pn532_spi) + driver_pn532_spi_enabled="yes" + DRIVERS_CFLAGS="$DRIVERS_CFLAGS -DDRIVER_PN532_SPI_ENABLED" + ;; *) AC_MSG_ERROR([Unknow driver: $driver]) ;; @@ -83,6 +88,7 @@ AC_DEFUN([LIBNFC_ARG_WITH_DRIVERS], AM_CONDITIONAL(DRIVER_PN53X_USB_ENABLED, [test x"$driver_pn53x_usb_enabled" = xyes]) 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]) ]) AC_DEFUN([LIBNFC_DRIVERS_SUMMARY],[ @@ -94,4 +100,5 @@ echo " acr122s.......... $driver_acr122s_enabled" 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" ])