/*- * Public platform independent Near Field Communication (NFC) library * * Copyright (C) 2010, Roel Verdult, Romuald Conty * Copyright (C) 2011, Romuald Conty, 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 pn532_uart.c * @brief PN532 driver using UART bus (UART, RS232, etc.) */ /* vim: set ts=2 sw=2 et: */ #ifdef HAVE_CONFIG_H # include "config.h" #endif // HAVE_CONFIG_H #include "pn532_uart.h" #include #include #include #include #include #include "drivers.h" #include "nfc-internal.h" #include "chips/pn53x.h" #include "chips/pn53x-internal.h" #include "uart.h" #define PN532_UART_DEFAULT_SPEED 115200 #define PN532_UART_DRIVER_NAME "pn532_uart" #define LOG_CATEGORY "libnfc.driver.pn532_uart" int pn532_uart_ack (nfc_device *pnd); int pn532_uart_wakeup (nfc_device *pnd); const struct pn53x_io pn532_uart_io; struct pn532_uart_data { serial_port port; #ifndef WIN32 int iAbortFds[2]; #else volatile bool abort_flag; #endif }; #define DRIVER_DATA(pnd) ((struct pn532_uart_data*)(pnd->driver_data)) bool pn532_uart_probe (nfc_connstring connstrings[], size_t connstrings_len, size_t *pszDeviceFound) { /** @note: Due to UART bus we can't know if its really a pn532 without * sending some PN53x commands. But using this way to probe devices, we can * have serious problem with other device on this bus */ #ifndef SERIAL_AUTOPROBE_ENABLED (void) connstrings; (void) connstrings_len; *pszDeviceFound = 0; log_put (LOG_CATEGORY, NFC_PRIORITY_INFO, "Serial auto-probing have been disabled at compile time. Skipping autoprobe."); return false; #else /* SERIAL_AUTOPROBE_ENABLED */ *pszDeviceFound = 0; serial_port sp; char **acPorts = uart_list_ports (); const char *acPort; int iDevice = 0; while ((acPort = acPorts[iDevice++])) { sp = uart_open (acPort); log_put (LOG_CATEGORY, NFC_PRIORITY_TRACE, "Trying to find PN532 device on serial port: %s at %d bauds.", acPort, PN532_UART_DEFAULT_SPEED); if ((sp != INVALID_SERIAL_PORT) && (sp != CLAIMED_SERIAL_PORT)) { // We need to flush input to be sure first reply does not comes from older byte transceive uart_flush_input (sp); // Serial port claimed but we need to check if a PN532_UART is connected. uart_set_speed (sp, PN532_UART_DEFAULT_SPEED); nfc_device *pnd = nfc_device_new (); pnd->driver = &pn532_uart_driver; pnd->driver_data = malloc(sizeof(struct pn532_uart_data)); DRIVER_DATA (pnd)->port = sp; // Alloc and init chip's data pn53x_data_new (pnd, &pn532_uart_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; #ifndef WIN32 // pipe-based abort mecanism pipe (DRIVER_DATA (pnd)->iAbortFds); #else DRIVER_DATA (pnd)->abort_flag = false; #endif // Check communication using "Diagnose" command, with "Communication test" (0x00) int res = pn53x_check_communication (pnd); pn53x_data_free (pnd); nfc_device_free (pnd); uart_close (sp); if(res < 0) { continue; } snprintf (connstrings[*pszDeviceFound], sizeof(nfc_connstring), "%s:%s:%"PRIu32, PN532_UART_DRIVER_NAME, acPort, PN532_UART_DEFAULT_SPEED); (*pszDeviceFound)++; // Test if we reach the maximum "wanted" devices if ((*pszDeviceFound) >= connstrings_len) break; } } iDevice = 0; while ((acPort = acPorts[iDevice++])) { free ((void*)acPort); } free (acPorts); #endif /* SERIAL_AUTOPROBE_ENABLED */ return true; } struct pn532_uart_descriptor { char port[128]; uint32_t speed; }; int pn532_connstring_decode (const nfc_connstring connstring, struct pn532_uart_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_UART_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; } nfc_device * pn532_uart_connect (const nfc_connstring connstring) { struct pn532_uart_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_UART_DEFAULT_SPEED; } serial_port sp; nfc_device *pnd = NULL; log_put (LOG_CATEGORY, NFC_PRIORITY_TRACE, "Attempt to connect to: %s at %d bauds.", ndd.port, ndd.speed); sp = uart_open (ndd.port); if (sp == INVALID_SERIAL_PORT) log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "Invalid serial port: %s", ndd.port); if (sp == CLAIMED_SERIAL_PORT) log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "Serial port already claimed: %s", ndd.port); if ((sp == CLAIMED_SERIAL_PORT) || (sp == INVALID_SERIAL_PORT)) return NULL; // We need to flush input to be sure first reply does not comes from older byte transceive uart_flush_input (sp); uart_set_speed (sp, ndd.speed); // We have a connection pnd = nfc_device_new (); snprintf (pnd->acName, sizeof (pnd->acName), "%s:%s", PN532_UART_DRIVER_NAME, ndd.port); pnd->driver_data = malloc(sizeof(struct pn532_uart_data)); DRIVER_DATA (pnd)->port = sp; // Alloc and init chip's data pn53x_data_new (pnd, &pn532_uart_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_uart_driver; #ifndef WIN32 // pipe-based abort mecanism pipe (DRIVER_DATA (pnd)->iAbortFds); #else DRIVER_DATA (pnd)->abort_flag = false; #endif // Check communication using "Diagnose" command, with "Communication test" (0x00) if (pn53x_check_communication (pnd) < 0) { nfc_perror (pnd, "pn53x_check_communication"); pn532_uart_disconnect (pnd); return NULL; } pn53x_init(pnd); return pnd; } void pn532_uart_disconnect (nfc_device *pnd) { // Release UART port uart_close (DRIVER_DATA(pnd)->port); #ifndef WIN32 // Release file descriptors used for abort mecanism close (DRIVER_DATA (pnd)->iAbortFds[0]); close (DRIVER_DATA (pnd)->iAbortFds[1]); #endif pn53x_data_free (pnd); nfc_device_free (pnd); } int pn532_uart_wakeup (nfc_device *pnd) { /* High Speed Unit (HSU) wake up consist to send 0x55 and wait a "long" delay for PN532 being wakeup. */ const uint8_t pn532_wakeup_preamble[] = { 0x55, 0x55, 0x00, 0x00, 0x00 }; int res = uart_send (DRIVER_DATA(pnd)->port, pn532_wakeup_preamble, sizeof (pn532_wakeup_preamble), 0); CHIP_DATA(pnd)->power_mode = NORMAL; // PN532 should now be awake return res; } #define PN532_BUFFER_LEN (PN53x_EXTENDED_FRAME__DATA_MAX_LEN + PN53x_EXTENDED_FRAME__OVERHEAD) int pn532_uart_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 uart_flush_input (DRIVER_DATA(pnd)->port); switch (CHIP_DATA(pnd)->power_mode) { case LOWVBAT: { /** PN532C106 wakeup. */ if ((res = pn532_uart_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 = pn53x_SAMConfiguration (pnd, 0x01, 500)) < 0) { return res; } } break; case POWERDOWN: { if ((res = pn532_uart_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 (pn53x_build_frame (abtFrame, &szFrame, pbtData, szData) < 0) { pnd->last_error = NFC_EINVARG; return pnd->last_error; } res = uart_send (DRIVER_DATA(pnd)->port, abtFrame, szFrame, timeout); if (res != 0) { log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "%s", "Unable to transmit data. (TX)"); pnd->last_error = res; return pnd->last_error; } uint8_t abtRxBuf[6]; res = uart_receive (DRIVER_DATA(pnd)->port, abtRxBuf, 6, 0, timeout); if (res != 0) { log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "%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_uart_receive (nfc_device *pnd, uint8_t *pbtData, const size_t szDataLen, int timeout) { uint8_t abtRxBuf[5]; size_t len; void *abort_p = NULL; #ifndef WIN32 abort_p = &(DRIVER_DATA (pnd)->iAbortFds[1]); #else abort_p = (void*)&(DRIVER_DATA (pnd)->abort_flag); #endif pnd->last_error = uart_receive (DRIVER_DATA (pnd)->port, abtRxBuf, 5, abort_p, timeout); if (abort_p && (NFC_EOPABORTED == pnd->last_error)) { return pn532_uart_ack (pnd); } if (pnd->last_error != 0) { log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "%s", "Unable to receive data. (RX)"); return pnd->last_error; } const uint8_t pn53x_preamble[3] = { 0x00, 0x00, 0xff }; if (0 != (memcmp (abtRxBuf, pn53x_preamble, 3))) { log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "%s", "Frame preamble+start code mismatch"); pnd->last_error = NFC_EIO; return pnd->last_error; } if ((0x01 == abtRxBuf[3]) && (0xff == abtRxBuf[4])) { // Error frame uart_receive (DRIVER_DATA (pnd)->port, abtRxBuf, 3, 0, timeout); log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "%s", "Application level error detected"); pnd->last_error = NFC_EIO; return pnd->last_error; } else if ((0xff == abtRxBuf[3]) && (0xff == abtRxBuf[4])) { // Extended frame pnd->last_error = uart_receive (DRIVER_DATA(pnd)->port, abtRxBuf, 3, 0, timeout); if (pnd->last_error) return pnd->last_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) { // TODO: Retry log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "%s", "Length checksum mismatch"); pnd->last_error = NFC_EIO; return pnd->last_error; } } else { // Normal frame if (256 != (abtRxBuf[3] + abtRxBuf[4])) { // TODO: Retry log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "%s", "Length checksum mismatch"); pnd->last_error = NFC_EIO; return pnd->last_error; } // abtRxBuf[3] (LEN) include TFI + (CC+1) len = abtRxBuf[3] - 2; } if (len > szDataLen) { log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "Unable to receive data: buffer too small. (szDataLen: %zu, len: %zu)", szDataLen, len); pnd->last_error = NFC_EIO; return pnd->last_error; } // TFI + PD0 (CC+1) pnd->last_error = uart_receive (DRIVER_DATA (pnd)->port, abtRxBuf, 2, 0, timeout); if (pnd->last_error != 0) { log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "%s", "Unable to receive data. (RX)"); return pnd->last_error; } if (abtRxBuf[0] != 0xD5) { log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "%s", "TFI Mismatch"); pnd->last_error = NFC_EIO; return pnd->last_error; } if (abtRxBuf[1] != CHIP_DATA (pnd)->last_command + 1) { log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "%s", "Command Code verification failed"); pnd->last_error = NFC_EIO; return pnd->last_error; } if (len) { pnd->last_error = uart_receive (DRIVER_DATA (pnd)->port, pbtData, len, 0, timeout); if (pnd->last_error != 0) { log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "%s", "Unable to receive data. (RX)"); return pnd->last_error; } } pnd->last_error = uart_receive (DRIVER_DATA (pnd)->port, abtRxBuf, 2, 0, timeout); if (pnd->last_error != 0) { log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "%s", "Unable to receive data. (RX)"); return pnd->last_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_CATEGORY, NFC_PRIORITY_ERROR, "%s", "Data checksum mismatch"); pnd->last_error = NFC_EIO; return pnd->last_error; } if (0x00 != abtRxBuf[1]) { log_put (LOG_CATEGORY, NFC_PRIORITY_ERROR, "%s", "Frame postamble mismatch"); pnd->last_error = NFC_EIO; return pnd->last_error; } // The PN53x command is done and we successfully received the reply return len; } int pn532_uart_ack (nfc_device *pnd) { int res = 0; if (POWERDOWN == CHIP_DATA(pnd)->power_mode) { if ((res = pn532_uart_wakeup(pnd)) < 0) { return res; } } return (uart_send (DRIVER_DATA(pnd)->port, pn53x_ack_frame, sizeof (pn53x_ack_frame), 0)); } int pn532_uart_abort_command (nfc_device *pnd) { if (pnd) { #ifndef WIN32 close (DRIVER_DATA (pnd)->iAbortFds[0]); pipe (DRIVER_DATA (pnd)->iAbortFds); #else DRIVER_DATA (pnd)->abort_flag = true; #endif } return NFC_SUCCESS; } const struct pn53x_io pn532_uart_io = { .send = pn532_uart_send, .receive = pn532_uart_receive, }; const struct nfc_driver_t pn532_uart_driver = { .name = PN532_UART_DRIVER_NAME, .probe = pn532_uart_probe, .connect = pn532_uart_connect, .disconnect = pn532_uart_disconnect, .strerror = pn53x_strerror, .initiator_init = pn53x_initiator_init, .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, .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, .abort_command = pn532_uart_abort_command, .idle = pn53x_idle, };