From 053dc8d5f81046bbcbb47d343aa409e697761378 Mon Sep 17 00:00:00 2001 From: Marcos Vives Del Sol Date: Mon, 15 Jun 2015 22:46:47 +0200 Subject: [PATCH] Basic support for RC522 detection --- libnfc/Makefile.am | 4 +- libnfc/chips/rc522-internal.h | 168 +++++++++++---------- libnfc/chips/rc522.c | 272 ++++++++++++++++++++++++++++++---- libnfc/chips/rc522.h | 7 +- libnfc/drivers/rc522_uart.c | 226 ++++++++++++++-------------- libnfc/nfc.c | 6 + libnfc/timing.c | 75 ++++++++++ libnfc/timing.h | 48 ++++++ 8 files changed, 587 insertions(+), 219 deletions(-) create mode 100644 libnfc/timing.c create mode 100644 libnfc/timing.h diff --git a/libnfc/Makefile.am b/libnfc/Makefile.am index dc3f27f..f3c5f20 100644 --- a/libnfc/Makefile.am +++ b/libnfc/Makefile.am @@ -20,7 +20,9 @@ libnfc_la_SOURCES = \ log-internal.h \ mirror-subr.h \ nfc-internal.h \ - target-subr.h + target-subr.h \ + timing.h \ + timing.c libnfc_la_LDFLAGS = -no-undefined -version-info 5:1:0 -export-symbols-regex '^nfc_|^iso14443a_|^iso14443b_|^str_nfc_|pn53x_transceive|pn532_SAMConfiguration|pn53x_read_register|pn53x_write_register' libnfc_la_CFLAGS = @DRIVERS_CFLAGS@ diff --git a/libnfc/chips/rc522-internal.h b/libnfc/chips/rc522-internal.h index 7d2923c..23a91e4 100644 --- a/libnfc/chips/rc522-internal.h +++ b/libnfc/chips/rc522-internal.h @@ -3,108 +3,124 @@ #ifndef __NFC_CHIPS_RC522_INTERNAL_H__ #define __NFC_CHIPS_RC522_INTERNAL_H__ -#define RC522_FIFO_SIZE 64 +#define FIFO_SIZE 64 -#define RC522_REG_CommandReg 0x01 -#define RC522_REG_CommandReg_RcvOff (1 << 5) -#define RC522_REG_CommandReg_PowerDown (1 << 4) +typedef enum { + RC522_UNKNOWN = 0x00, + FM17522 = 0x88, + MFRC522_V1 = 0x91, + MFRC522_V2 = 0x92 +} rc522_type; -#define RC522_REG_ComlEnReg 0x02 +typedef enum { + CMD_IDLE = 0x0, + CMD_MEM = 0x1, + CMD_GENRANDOMID = 0x2, + CMD_CALCCRC = 0x3, + CMD_TRANSMIT = 0x4, + CMD_NOCMDCHANGE = 0x7, + CMD_RECEIVE = 0x8, + CMD_TRANSCEIVE = 0xC, + CMD_MFAUTHENT = 0xE, + CMD_SOFTRESET = 0xF +} rc522_cmd; -#define RC522_REG_DivlEnReg 0x03 +#define REG_CommandReg 0x01 +#define REG_CommandReg_RcvOff (1 << 5) +#define REG_CommandReg_PowerDown (1 << 4) -#define RC522_REG_ComIrqReg 0x04 +#define REG_ComlEnReg 0x02 -#define RC522_REG_DivIrqReg 0x05 +#define REG_DivlEnReg 0x03 -#define RC522_REG_ErrorReg 0x06 +#define REG_ComIrqReg 0x04 -#define RC522_REG_Status1Reg 0x07 +#define REG_DivIrqReg 0x05 +#define REG_DivIrqReg_MfinActIRq (1 << 4) +#define REG_DivIrqReg_CRCIRq (1 << 2) -#define RC522_REG_Status2Reg 0x08 -#define RC522_REG_Status2Reg_MFCrypto1On (1 << 3) +#define REG_ErrorReg 0x06 -#define RC522_REG_FIFODataReg 0x09 +#define REG_Status1Reg 0x07 -#define RC522_REG_FIFOLevelReg 0x0A +#define REG_Status2Reg 0x08 +#define REG_Status2Reg_MFCrypto1On (1 << 3) -#define RC522_REG_WaterLevelReg 0x0B +#define REG_FIFODataReg 0x09 -#define RC522_REG_ControlReg 0x0C +#define REG_FIFOLevelReg 0x0A -#define RC522_REG_BitFramingReg 0x0D +#define REG_WaterLevelReg 0x0B -#define RC522_REG_CollReg 0x0E +#define REG_ControlReg 0x0C -#define RC522_REG_ModeReg 0x11 +#define REG_BitFramingReg 0x0D -#define RC522_REG_TxModeReg 0x12 -#define RC522_REG_TxModeReg_TxCRCEn (1 << 7) -#define RC522_REG_TxModeReg_TxSpeed_106k (0 << 4) -#define RC522_REG_TxModeReg_TxSpeed_212k (1 << 4) -#define RC522_REG_TxModeReg_TxSpeed_424k (2 << 4) -#define RC522_REG_TxModeReg_TxSpeed_847k (3 << 4) -#define RC522_REG_TxModeReg_TxSpeed_MASK (7 << 4) +#define REG_CollReg 0x0E -#define RC522_REG_RxModeReg 0x13 -#define RC522_REG_RxModeReg_RxCRCEn (1 << 7) -#define RC522_REG_RxModeReg_RxSpeed_106k (0 << 4) -#define RC522_REG_RxModeReg_RxSpeed_212k (1 << 4) -#define RC522_REG_RxModeReg_RxSpeed_424k (2 << 4) -#define RC522_REG_RxModeReg_RxSpeed_847k (3 << 4) -#define RC522_REG_RxModeReg_RxSpeed_MASK (7 << 4) +#define REG_ModeReg 0x11 -#define RC522_REG_TxControlReg 0x14 -#define RC522_REG_TxControlReg_Tx2RFEn (1 << 1) -#define RC522_REG_TxControlReg_Tx1RFEn (1 << 0) +#define REG_TxModeReg 0x12 +#define REG_TxModeReg_TxCRCEn (1 << 7) +#define REG_TxModeReg_TxSpeed_106k (0 << 4) +#define REG_TxModeReg_TxSpeed_212k (1 << 4) +#define REG_TxModeReg_TxSpeed_424k (2 << 4) +#define REG_TxModeReg_TxSpeed_847k (3 << 4) +#define REG_TxModeReg_TxSpeed_MASK (7 << 4) -#define RC522_REG_TxASKReg 0x15 +#define REG_RxModeReg 0x13 +#define REG_RxModeReg_RxCRCEn (1 << 7) +#define REG_RxModeReg_RxSpeed_106k (0 << 4) +#define REG_RxModeReg_RxSpeed_212k (1 << 4) +#define REG_RxModeReg_RxSpeed_424k (2 << 4) +#define REG_RxModeReg_RxSpeed_847k (3 << 4) +#define REG_RxModeReg_RxSpeed_MASK (7 << 4) -#define RC522_REG_TxSelReg 0x16 +#define REG_TxControlReg 0x14 +#define REG_TxControlReg_Tx2RFEn (1 << 1) +#define REG_TxControlReg_Tx1RFEn (1 << 0) -#define RC522_REG_RxSelReg 0x17 +#define REG_TxASKReg 0x15 -#define RC522_REG_RxThresholdReg 0x18 +#define REG_TxSelReg 0x16 -#define RC522_REG_DemodReg 0x19 +#define REG_RxSelReg 0x17 -#define RC522_REG_MfTxReg 0x1C +#define REG_RxThresholdReg 0x18 -#define RC522_REG_MfRxReg 0x1D -#define RC522_REG_MfRxReg_ParityDisable (1 << 4) +#define REG_DemodReg 0x19 -#define RC522_REG_SerialSpeedReg 0x1F -#define RC522_REG_CRCResultReg 0x21 -#define RC522_REG_ModWidthReg 0x24 -#define RC522_REG_RFCfgReg 0x26 -#define RC522_REG_GsNReg 0x27 -#define RC522_REG_CWGsPReg 0x28 -#define RC522_REG_ModGsPReg 0x29 -#define RC522_REG_TModeReg 0x2A -#define RC522_REG_TPrescalerReg 0x2B -#define RC522_REG_TReloadReg 0x2C -#define RC522_REG_TCounterValReg 0x2E -#define RC522_REG_TestSel1Reg 0x31 -#define RC522_REG_TestSel2Reg 0x32 -#define RC522_REG_TestPinEnReg 0x33 -#define RC522_REG_TestPinValueReg 0x34 -#define RC522_REG_TestBusReg 0x35 -#define RC522_REG_AutoTestReg 0x36 -#define RC522_REG_VersionReg 0x37 -#define RC522_REG_AnalogTestReg 0x38 -#define RC522_REG_TestDAC1Reg 0x39 -#define RC522_REG_TestDAC2Reg 0x3A -#define RC522_REG_TestADCReg 0x3B +#define REG_MfTxReg 0x1C -#define RC522_CMD_Idle 0x0 -#define RC522_CMD_Mem 0x1 -#define RC522_CMD_GenerateRandomId 0x2 -#define RC522_CMD_CalcCRC 0x3 -#define RC522_CMD_Transmit 0x4 -#define RC522_CMD_NoCmdChange 0x7 -#define RC522_CMD_Receive 0x8 -#define RC522_CMD_Transceive 0xC -#define RC522_CMD_MFAuthent 0xE -#define RC522_CMD_SoftReset 0xF +#define REG_MfRxReg 0x1D +#define REG_MfRxReg_ParityDisable (1 << 4) + +#define REG_SerialSpeedReg 0x1F +#define REG_CRCResultReg 0x21 +#define REG_ModWidthReg 0x24 +#define REG_RFCfgReg 0x26 +#define REG_GsNReg 0x27 +#define REG_CWGsPReg 0x28 +#define REG_ModGsPReg 0x29 +#define REG_TModeReg 0x2A +#define REG_TPrescalerReg 0x2B +#define REG_TReloadReg 0x2C +#define REG_TCounterValReg 0x2E +#define REG_TestSel1Reg 0x31 +#define REG_TestSel2Reg 0x32 +#define REG_TestPinEnReg 0x33 +#define REG_TestPinValueReg 0x34 +#define REG_TestBusReg 0x35 + +#define REG_AutoTestReg 0x36 +#define REG_AutoTestReg_SelfTest_Disabled (0x0 << 0) +#define REG_AutoTestReg_SelfTest_Enabled (0x9 << 0) +#define REG_AutoTestReg_SelfTest_MASK (0xF << 0) + +#define REG_VersionReg 0x37 +#define REG_AnalogTestReg 0x38 +#define REG_TestDAC1Reg 0x39 +#define REG_TestDAC2Reg 0x3A +#define REG_TestADCReg 0x3B #endif diff --git a/libnfc/chips/rc522.c b/libnfc/chips/rc522.c index ca195c5..5ba222f 100644 --- a/libnfc/chips/rc522.c +++ b/libnfc/chips/rc522.c @@ -23,12 +23,11 @@ #include "nfc/nfc.h" #include "nfc-internal.h" +#include "timing.h" #define LOG_CATEGORY "libnfc.chip.rc522" #define LOG_GROUP NFC_LOG_GROUP_CHIP -#define RC522_TIMEOUT 5 - const nfc_modulation_type rc522_initiator_modulation[] = { NMT_ISO14443A, 0 }; const nfc_modulation_type rc522_target_modulation[] = { 0 }; @@ -36,6 +35,7 @@ const nfc_baud_rate rc522_iso14443a_supported_baud_rates[] = { NBR_847, NBR_424, struct rc522_chip_data { const struct rc522_io * io; + rc522_type version; }; #define CHIP_DATA(x) ((struct rc522_chip_data *) (x)->chip_data) @@ -48,14 +48,51 @@ int rc522_data_new(struct nfc_device * pnd, const struct rc522_io * io) { } CHIP_DATA(pnd)->io = io; + CHIP_DATA(pnd)->version = RC522_UNKNOWN; + return NFC_SUCCESS; +} + +void rc522_data_free(struct nfc_device * pnd) { + free(pnd->chip_data); +} + +int rc522_read_bulk(struct nfc_device * pnd, uint8_t reg, uint8_t * val, size_t len) { + int ret = CHIP_DATA(pnd)->io->read(pnd, reg, val, len); + if (ret) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Unable to read register %02X!", reg); + return ret; + } + +#ifdef LOG + char action[8]; + snprintf(action, sizeof(action), "RD %02X", reg); + LOG_HEX(NFC_LOG_GROUP_CHIP, action, val, len); +#endif + + return NFC_SUCCESS; +} + +int rc522_write_bulk(struct nfc_device * pnd, uint8_t reg, const uint8_t * val, size_t len) { + int ret = CHIP_DATA(pnd)->io->write(pnd, reg, val, len); + if (ret) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Unable to write register %02X!", reg); + return ret; + } + +#ifdef LOG + char action[8]; + snprintf(action, sizeof(action), "WR %02X", reg); + LOG_HEX(NFC_LOG_GROUP_CHIP, action, val, len); +#endif + return NFC_SUCCESS; } int rc522_read_reg(struct nfc_device * pnd, uint8_t reg) { uint8_t val; + int ret; - int ret = CHIP_DATA(pnd)->io->read(pnd, reg, &val, 1); - if (ret < 0) { + if ((ret = rc522_read_bulk(pnd, reg, &val, 1)) < 0) { return ret; } @@ -72,32 +109,99 @@ int rc522_write_reg(struct nfc_device * pnd, uint8_t reg, uint8_t val, uint8_t m val = (val & mask) | (oldval & ~mask); } - return CHIP_DATA(pnd)->io->write(pnd, reg, &val, 1); + return rc522_write_bulk(pnd, reg, &val, 1); +} + +int rc522_start_command(struct nfc_device * pnd, rc522_cmd cmd) { + bool needsRX = false; + + // Disabling RX saves energy, so based on the command we'll also update the RxOff flag + switch (cmd) { + case CMD_IDLE: + case CMD_MEM: + case CMD_GENRANDOMID: + case CMD_CALCCRC: + case CMD_TRANSMIT: + case CMD_SOFTRESET: + break; + + case CMD_RECEIVE: + case CMD_TRANSCEIVE: + case CMD_MFAUTHENT: + needsRX = true; + break; + + case CMD_NOCMDCHANGE: + return NFC_SUCCESS; + + default: + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Attempted to execute non-existant command: %02X", cmd); + pnd->last_error = NFC_ESOFT; + return pnd->last_error; + } + + uint8_t regval = cmd; + if (!needsRX) { + regval |= REG_CommandReg_RcvOff; + } + + return rc522_write_reg(pnd, REG_CommandReg, regval, 0xFF); +} + +int rc522_wait_wakeup(struct nfc_device * pnd) { + // NXP does not mention in the datasheet how much time does it take for RC522 to come back to life, so we'll wait up to 50ms + timeout_t to; + timeout_init(&to, 50); + + // rc522_read_reg updates last_error. Backup it to ignore timeouts + int last_error = pnd->last_error; + + while (timeout_check(&to)) { + int ret = rc522_read_reg(pnd, REG_CommandReg); + if (ret < 0 && ret != NFC_ETIMEOUT) { + return ret; + } + + // If the powerdown bit is zero the RC522 is ready to kick asses! + if ((ret & REG_CommandReg_PowerDown) == 0) { + pnd->last_error = last_error; + return NFC_SUCCESS; + } + } + + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "rc522_wait_wakeup timeout!"); + pnd->last_error = NFC_ETIMEOUT; + return pnd->last_error; +} + +int rc522_soft_reset(struct nfc_device * pnd) { + return + rc522_start_command(pnd, CMD_SOFTRESET) || + rc522_wait_wakeup(pnd); } int rc522_set_baud_rate(struct nfc_device * pnd, nfc_baud_rate speed) { uint8_t txVal, rxVal; - int ret; switch (speed) { case NBR_106: - txVal = RC522_REG_TxModeReg_TxSpeed_106k; - rxVal = RC522_REG_RxModeReg_RxSpeed_106k; + txVal = REG_TxModeReg_TxSpeed_106k; + rxVal = REG_RxModeReg_RxSpeed_106k; break; case NBR_212: - txVal = RC522_REG_TxModeReg_TxSpeed_212k; - rxVal = RC522_REG_RxModeReg_RxSpeed_212k; + txVal = REG_TxModeReg_TxSpeed_212k; + rxVal = REG_RxModeReg_RxSpeed_212k; break; case NBR_424: - txVal = RC522_REG_TxModeReg_TxSpeed_424k; - rxVal = RC522_REG_RxModeReg_RxSpeed_424k; + txVal = REG_TxModeReg_TxSpeed_424k; + rxVal = REG_RxModeReg_RxSpeed_424k; break; case NBR_847: - txVal = RC522_REG_TxModeReg_TxSpeed_847k; - rxVal = RC522_REG_RxModeReg_RxSpeed_847k; + txVal = REG_TxModeReg_TxSpeed_847k; + rxVal = REG_RxModeReg_RxSpeed_847k; break; default: @@ -105,8 +209,8 @@ int rc522_set_baud_rate(struct nfc_device * pnd, nfc_baud_rate speed) { } return - rc522_write_reg(pnd, RC522_REG_TxModeReg, txVal, RC522_REG_TxModeReg_TxSpeed_MASK) || - rc522_write_reg(pnd, RC522_REG_RxModeReg, rxVal, RC522_REG_RxModeReg_RxSpeed_MASK); + rc522_write_reg(pnd, REG_TxModeReg, txVal, REG_TxModeReg_TxSpeed_MASK) || + rc522_write_reg(pnd, REG_RxModeReg, rxVal, REG_RxModeReg_RxSpeed_MASK); } int rc522_initiator_select_passive_target_ext(struct nfc_device * pnd, const nfc_modulation nm, const uint8_t * pbtInitData, const size_t szInitData, nfc_target * pnt, int timeout) @@ -173,8 +277,9 @@ int rc522_set_property_bool(struct nfc_device * pnd, const nfc_property property return NFC_SUCCESS; } - ret = rc522_write_reg(pnd, RC522_REG_TxModeReg, enable ? ~0 : 0, RC522_REG_TxModeReg_TxCRCEn) || - rc522_write_reg(pnd, RC522_REG_RxModeReg, enable ? ~0 : 0, RC522_REG_RxModeReg_RxCRCEn); + ret = + rc522_write_reg(pnd, REG_TxModeReg, enable ? ~0 : 0, REG_TxModeReg_TxCRCEn) || + rc522_write_reg(pnd, REG_RxModeReg, enable ? ~0 : 0, REG_RxModeReg_RxCRCEn); if (ret) { return ret; } @@ -187,7 +292,7 @@ int rc522_set_property_bool(struct nfc_device * pnd, const nfc_property property return NFC_SUCCESS; } - ret = rc522_write_reg(pnd, RC522_REG_MfRxReg, enable ? 0 : ~0, RC522_REG_MfRxReg_ParityDisable); + ret = rc522_write_reg(pnd, REG_MfRxReg, enable ? 0 : ~0, REG_MfRxReg_ParityDisable); if (ret) { return ret; } @@ -200,10 +305,10 @@ int rc522_set_property_bool(struct nfc_device * pnd, const nfc_property property return NFC_SUCCESS; case NP_ACTIVATE_FIELD: - return rc522_write_reg(pnd, RC522_REG_TxControlReg, enable ? ~0 : 0, RC522_REG_TxControlReg_Tx2RFEn | RC522_REG_TxControlReg_Tx1RFEn); + return rc522_write_reg(pnd, REG_TxControlReg, enable ? ~0 : 0, REG_TxControlReg_Tx2RFEn | REG_TxControlReg_Tx1RFEn); case NP_ACTIVATE_CRYPTO1: - return rc522_write_reg(pnd, RC522_REG_Status2Reg, enable ? ~0 : 0, RC522_REG_Status2Reg_MFCrypto1On); + return rc522_write_reg(pnd, REG_Status2Reg, enable ? ~0 : 0, REG_Status2Reg_MFCrypto1On); case NP_FORCE_ISO14443_A: // ISO14443-A is the only mode supported by MFRC522 @@ -214,8 +319,13 @@ int rc522_set_property_bool(struct nfc_device * pnd, const nfc_property property return NFC_SUCCESS; } - return rc522_set_baud_rate(pnd, NBR_106); - + int ret = rc522_set_baud_rate(pnd, NBR_106); + if (ret) { + pnd->last_error = ret; + } + return ret; + + case NP_ACCEPT_MULTIPLE_FRAMES: case NP_AUTO_ISO14443_4: case NP_ACCEPT_INVALID_FRAMES: case NP_INFINITE_SELECT: @@ -223,9 +333,11 @@ int rc522_set_property_bool(struct nfc_device * pnd, const nfc_property property case NP_TIMEOUT_COMMAND: case NP_TIMEOUT_ATR: case NP_TIMEOUT_COM: + pnd->last_error = NFC_EINVARG; return NFC_EINVARG; } + pnd->last_error = NFC_EINVARG; return NFC_EINVARG; } @@ -234,8 +346,116 @@ int rc522_set_property_int(struct nfc_device * pnd, const nfc_property property, return NFC_ENOTIMPL; } -int rc522_idle(struct nfc_device * pnd) { - // Set idle and disable RX demodulator to save energy - return rc522_write_reg(pnd, RC522_REG_CommandReg, RC522_CMD_Idle | RC522_REG_CommandReg_PowerDown, 0xFF); +int rc522_abort(struct nfc_device * pnd) { + return rc522_start_command(pnd, CMD_IDLE); +} + +int rc522_powerdown(struct nfc_device * pnd) { + return rc522_write_reg(pnd, REG_CommandReg, REG_CommandReg_RcvOff | REG_CommandReg_PowerDown | CMD_IDLE, 0xFF); +} + +// NXP MFRC522 datasheet section 16.1.1 +const uint8_t MFRC522_V1_SELFTEST[FIFO_SIZE] = { + 0x00, 0xC6, 0x37, 0xD5, 0x32, 0xB7, 0x57, 0x5C, 0xC2, 0xD8, 0x7C, 0x4D, 0xD9, 0x70, 0xC7, 0x73, + 0x10, 0xE6, 0xD2, 0xAA, 0x5E, 0xA1, 0x3E, 0x5A, 0x14, 0xAF, 0x30, 0x61, 0xC9, 0x70, 0xDB, 0x2E, + 0x64, 0x22, 0x72, 0xB5, 0xBD, 0x65, 0xF4, 0xEC, 0x22, 0xBC, 0xD3, 0x72, 0x35, 0xCD, 0xAA, 0x41, + 0x1F, 0xA7, 0xF3, 0x53, 0x14, 0xDE, 0x7E, 0x02, 0xD9, 0x0F, 0xB5, 0x5E, 0x25, 0x1D, 0x29, 0x79 +}; +const uint8_t MFRC522_V2_SELFTEST[FIFO_SIZE] = { + 0x00, 0xEB, 0x66, 0xBA, 0x57, 0xBF, 0x23, 0x95, 0xD0, 0xE3, 0x0D, 0x3D, 0x27, 0x89, 0x5C, 0xDE, + 0x9D, 0x3B, 0xA7, 0x00, 0x21, 0x5B, 0x89, 0x82, 0x51, 0x3A, 0xEB, 0x02, 0x0C, 0xA5, 0x00, 0x49, + 0x7C, 0x84, 0x4D, 0xB3, 0xCC, 0xD2, 0x1B, 0x81, 0x5D, 0x48, 0x76, 0xD5, 0x71, 0x61, 0x21, 0xA9, + 0x86, 0x96, 0x83, 0x38, 0xCF, 0x9D, 0x5B, 0x6D, 0xDC, 0x15, 0xBA, 0x3E, 0x7D, 0x95, 0x3B, 0x2F +}; + +// Extracted from a FM17522 with version 0x88. Fudan Semiconductor datasheet does not include it, though. +const uint8_t FM17522_SELFTEST[FIFO_SIZE] = { + 0x00, 0xD6, 0x78, 0x8C, 0xE2, 0xAA, 0x0C, 0x18, 0x2A, 0xB8, 0x7A, 0x7F, 0xD3, 0x6A, 0xCF, 0x0B, + 0xB1, 0x37, 0x63, 0x4B, 0x69, 0xAE, 0x91, 0xC7, 0xC3, 0x97, 0xAE, 0x77, 0xF4, 0x37, 0xD7, 0x9B, + 0x7C, 0xF5, 0x3C, 0x11, 0x8F, 0x15, 0xC3, 0xD7, 0xC1, 0x5B, 0x00, 0x2A, 0xD0, 0x75, 0xDE, 0x9E, + 0x51, 0x64, 0xAB, 0x3E, 0xE9, 0x15, 0xB5, 0xAB, 0x56, 0x9A, 0x98, 0x82, 0x26, 0xEA, 0x2A, 0x62 +}; + +int rc522_self_test(struct nfc_device * pnd) { + int version = rc522_read_reg(pnd, REG_VersionReg); + if (version < 0) { + return version; + } + + const uint8_t * correct; + switch (version) { + case MFRC522_V1: + correct = MFRC522_V1_SELFTEST; + break; + + case MFRC522_V2: + correct = MFRC522_V2_SELFTEST; + break; + + case FM17522: + correct = FM17522_SELFTEST; + break; + + default: + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Unknown chip version: 0x%02X", version); + return NFC_ECHIP; + } + + int ret; + uint8_t zeroes[25]; + memset(zeroes, 0x00, sizeof(zeroes)); + // MFRC522 datasheet section 16.1.1 + ret = + // 1. Perform a soft reset + rc522_soft_reset(pnd) || + // 2. Clear the internal buffer by writing 25 bytes of 0x00 and execute the Mem command + rc522_write_bulk(pnd, REG_FIFODataReg, zeroes, sizeof(zeroes)) || + rc522_start_command(pnd, CMD_MEM) || + // 3. Enable the self test by writing 0x09 to the AutoTestReg register + rc522_write_reg(pnd, REG_AutoTestReg, REG_AutoTestReg_SelfTest_Enabled, REG_AutoTestReg_SelfTest_MASK) || + // 4. Write 0x00h to the FIFO buffer + rc522_write_reg(pnd, REG_FIFODataReg, 0x00, 0xFF) || + // 5. Start the self test with the CalcCRC command + rc522_start_command(pnd, CMD_CALCCRC); + if (ret) { + return ret; + } + + // 6. Wait for the RC522 to calculate the selftest values + // The official datasheet does not mentions how much time does it take, let's use 5ms + timeout_t to; + timeout_init(&to, 5); + + while (1) { + if (!timeout_check(&to)) { + return NFC_ETIMEOUT; + } + + if ((ret = rc522_read_reg(pnd, REG_DivIrqReg)) < 0) { + return ret; + } + + // If the RC522 has finished calculating the CRC proceed + if (ret & REG_DivIrqReg_CRCIRq) { + break; + } + } + + uint8_t response[FIFO_SIZE]; + ret = + // 7. Read selftest result + rc522_read_bulk(pnd, REG_FIFODataReg, response, FIFO_SIZE) || + // 8. Disable selftest operation mode + rc522_write_reg(pnd, REG_AutoTestReg, REG_AutoTestReg_SelfTest_Disabled, REG_AutoTestReg_SelfTest_MASK); + if (ret) { + return ret; + } + + if (memcmp(correct, response, FIFO_SIZE) != 0) { + return NFC_ECHIP; + } + + CHIP_DATA(pnd)->version = version; + return NFC_SUCCESS; } diff --git a/libnfc/chips/rc522.h b/libnfc/chips/rc522.h index 4a2a90a..c266f55 100644 --- a/libnfc/chips/rc522.h +++ b/libnfc/chips/rc522.h @@ -28,10 +28,15 @@ struct rc522_io { }; int rc522_data_new(struct nfc_device * pnd, const struct rc522_io * io); +void rc522_data_free(struct nfc_device * pnd); +int rc522_self_test(struct nfc_device * pnd); +int rc522_wait_wakeup(struct nfc_device * pnd); + int rc522_get_supported_modulation(nfc_device * pnd, const nfc_mode mode, const nfc_modulation_type ** const supported_mt); int rc522_get_supported_baud_rate(nfc_device * pnd, const nfc_mode mode, const nfc_modulation_type nmt, const nfc_baud_rate ** const supported_br); int rc522_set_property_bool(struct nfc_device * pnd, const nfc_property property, const bool enable); int rc522_set_property_int(struct nfc_device * pnd, const nfc_property property, const int value); -int rc522_idle(struct nfc_device * pnd); +int rc522_abort(struct nfc_device * pnd); +int rc522_powerdown(struct nfc_device * pnd); #endif diff --git a/libnfc/drivers/rc522_uart.c b/libnfc/drivers/rc522_uart.c index 8d49b44..b3d9975 100644 --- a/libnfc/drivers/rc522_uart.c +++ b/libnfc/drivers/rc522_uart.c @@ -28,6 +28,8 @@ #include #include +#include +#include #include @@ -51,98 +53,90 @@ struct rc522_uart_data { #define DRIVER_DATA(pnd) ((struct rc522_uart_data*)(pnd->driver_data)) -static size_t -rc522_uart_scan(const nfc_context *context, nfc_connstring connstrings[], const size_t connstrings_len) -{ - size_t device_found = 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_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Trying to find RC522 device on serial port: %s at %d baud.", acPort, RC522_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, true); - // Serial port claimed but we need to check if a RC522_UART is opened. - uart_set_speed(sp, RC522_UART_DEFAULT_SPEED); - - nfc_connstring connstring; - snprintf(connstring, sizeof(nfc_connstring), "%s:%s:%"PRIu32, RC522_UART_DRIVER_NAME, acPort, RC522_UART_DEFAULT_SPEED); - nfc_device *pnd = nfc_device_new(context, connstring); - if (!pnd) { - perror("malloc"); - uart_close(sp); - iDevice = 0; - while ((acPort = acPorts[iDevice++])) { - free((void *)acPort); - } - free(acPorts); - return 0; - } - pnd->driver = &rc522_uart_driver; - pnd->driver_data = sp; - - // Alloc and init chip's data - if (rc522_data_new(pnd, &rc522_uart_io) == NULL) { - perror("rc522_data_new"); - uart_close(DRIVER_DATA(pnd)->port); - nfc_device_free(pnd); - iDevice = 0; - while ((acPort = acPorts[iDevice++])) { - free((void *)acPort); - } - free(acPorts); - return 0; - } - // SAMConfiguration command if needed to wakeup the chip and rc522_SAMConfiguration check if the chip is a RC522 - CHIP_DATA(pnd)->type = RC522; - // This device starts in LowVBat power mode - CHIP_DATA(pnd)->power_mode = LOWVBAT; - - // Check communication using "Diagnose" command, with "Communication test" (0x00) - int res = rc522_check_communication(pnd); - uart_close(DRIVER_DATA(pnd)->port); - rc522_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 ((acPort = acPorts[iDevice++])) { - free((void *)acPort); - } - free(acPorts); - return device_found; -} - void rc522_uart_close(nfc_device * pnd) { - rc522_idle(pnd); +// rc522_idle(pnd); // Release UART port uart_close(DRIVER_DATA(pnd)->port); - free(DRIVER_DATA(pnd)); - rc522_data_free(pnd); nfc_device_free(pnd); } +size_t rc522_uart_scan(const nfc_context * context, nfc_connstring connstrings[], const size_t connstrings_len) { + size_t device_found = 0; + serial_port sp; + char ** acPorts = uart_list_ports(); + const char * acPort; + size_t iDevice = 0; + + while ((acPort = acPorts[iDevice++])) { + sp = uart_open(acPort); + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Trying to find RC522 device on serial port: %s at %d baud.", acPort, RC522_UART_DEFAULT_SPEED); + + if (sp == INVALID_SERIAL_PORT || sp == CLAIMED_SERIAL_PORT) { + continue; + } + + // We need to flush input to be sure first reply does not comes from older byte transceive + uart_flush_input(sp, true); + // Serial port claimed but we need to check if a RC522_UART is opened. + uart_set_speed(sp, RC522_UART_DEFAULT_SPEED); + + nfc_connstring connstring; + snprintf(connstring, sizeof(nfc_connstring), "%s:%s:%"PRIu32, RC522_UART_DRIVER_NAME, acPort, RC522_UART_DEFAULT_SPEED); + nfc_device * pnd = nfc_device_new(context, connstring); + if (!pnd) { + perror("nfc_device_new"); + uart_close(sp); + nfc_device_free(pnd); + uart_list_free(acPorts); + return 0; + } + pnd->driver = &rc522_uart_driver; + + pnd->driver_data = malloc(sizeof(struct rc522_uart_data)); + if (!pnd->driver_data) { + perror("malloc"); + uart_close(sp); + nfc_device_free(pnd); + uart_list_free(acPorts); + return 0; + } + DRIVER_DATA(pnd)->port = sp; + + // Alloc and init chip's data + if (rc522_data_new(pnd, &rc522_uart_io)) { + perror("rc522_data_new"); + uart_close(sp); + nfc_device_free(pnd); + uart_list_free(acPorts); + return 0; + } + + // Check communication using self test + int res = rc522_self_test(pnd); + rc522_uart_close(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; + } + + uart_list_free(acPorts); + return device_found; +} + struct nfc_device * rc522_uart_open(const nfc_context * context, const nfc_connstring connstring) { char * port_str; char * baud_str; uint32_t baudrate; + char * endptr; int decodelvl = connstring_decode(connstring, RC522_UART_DRIVER_NAME, NULL, &port_str, &baud_str); switch (decodelvl) { @@ -151,20 +145,20 @@ struct nfc_device * rc522_uart_open(const nfc_context * context, const nfc_conns break; case 3: // Got port and baud rate - char * endptr; - baudrate = (uint32_t) strtol(speed_s, &endptr, 10); + // TODO: set baud rate AFTER initialization + baudrate = (uint32_t) strtol(baud_str, &endptr, 10); if (*endptr != '\0') { free(port_str); - free(bps_str); + free(baud_str); return NULL; } - free(bps_str); + free(baud_str); break; default: // Got unparseable gibberish free(port_str); - free(bps_str); + free(baud_str); return NULL; } @@ -172,7 +166,7 @@ struct nfc_device * rc522_uart_open(const nfc_context * context, const nfc_conns struct nfc_device * pnd = NULL; log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Attempt to open: %s at %d baud.", port_str, baudrate); - sp = uart_open(ndd.port); + sp = uart_open(port_str); if (sp == INVALID_SERIAL_PORT) { log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Invalid serial port: %s", port_str); @@ -212,51 +206,53 @@ struct nfc_device * rc522_uart_open(const nfc_context * context, const nfc_conns DRIVER_DATA(pnd)->port = sp; // Alloc and init chip's data - if (rc522_data_new(pnd, &rc522_uart_io) == NULL) { + if (!rc522_data_new(pnd, &rc522_uart_io)) { perror("rc522_data_new"); uart_close(sp); nfc_device_free(pnd); return NULL; } - if (rc522_check_communication(pnd) < 0) { - log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "rc522_check_communication error"); + if (rc522_self_test(pnd)) { + log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "rc522_self_test error"); rc522_uart_close(pnd); return NULL; } - rc522_init(pnd); return pnd; } -int rc522_uart_wakeup(struct nfc_device *pnd) { +int rc522_uart_wakeup(struct nfc_device * pnd) { + int ret; + /* High Speed Unit (HSU) wake up consist to send 0x55 and wait a "long" delay for RC522 being wakeup. */ const uint8_t rc522_wakeup_preamble[] = { 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - int res = uart_send(DRIVER_DATA(pnd)->port, rc522_wakeup_preamble, sizeof(rc522_wakeup_preamble), 0); - CHIP_DATA(pnd)->power_mode = NORMAL; // RC522 should now be awake - return res; + if ((ret = uart_send(DRIVER_DATA(pnd)->port, rc522_wakeup_preamble, sizeof(rc522_wakeup_preamble), 0)) < 0) { + return ret; + } + + return rc522_wait_wakeup(pnd); } #define READ 1 #define WRITE 0 uint8_t rc522_uart_pack(int reg, int op) { assert(reg < 64); - assert(op == READ | op == WRITE); + assert(op == READ || op == WRITE); return op << 7 | reg; } int rc522_uart_read(struct nfc_device * pnd, uint8_t reg, uint8_t * data, size_t size) { uint8_t cmd = rc522_uart_pack(reg, READ); + int ret; while (size > 0) { - pnd->last_error = uart_send(pnd->driver_data, &cmd, 1, RC522_UART_IO_TIMEOUT); - if (pnd->last_error < 0) { + if ((ret = uart_send(pnd->driver_data, &cmd, 1, RC522_UART_IO_TIMEOUT)) < 0) { goto error; } - pnd->last_error = uart_receive(pnd->driver_data, data, 1, RC522_UART_IO_TIMEOUT); - if (pnd->last_error < 0) { + if ((ret = uart_receive(pnd->driver_data, data, 1, NULL, RC522_UART_IO_TIMEOUT)) < 0) { goto error; } @@ -271,7 +267,7 @@ error: return pnd->last_error; } -int rc522_uart_write(struct nfc_device * pnd, uint8_t reg, uint8_t * data, size_t size) { +int rc522_uart_write(struct nfc_device * pnd, uint8_t reg, const uint8_t * data, size_t size) { uint8_t cmd = rc522_uart_pack(reg, WRITE); while (size > 0) { @@ -283,7 +279,7 @@ int rc522_uart_write(struct nfc_device * pnd, uint8_t reg, uint8_t * data, size_ // Second: wait for a reply uint8_t reply; - pnd->last_error = uart_receive(pnd->driver_data, &reply, 1, RC522_UART_IO_TIMEOUT); + pnd->last_error = uart_receive(pnd->driver_data, &reply, 1, NULL, RC522_UART_IO_TIMEOUT); if (pnd->last_error < 0) { return pnd->last_error; } @@ -322,20 +318,20 @@ const struct nfc_driver rc522_uart_driver = { .scan = rc522_uart_scan, .open = rc522_uart_open, .close = rc522_uart_close, - .strerror = rc522_strerror, +// .strerror = rc522_strerror, - .initiator_init = rc522_initiator_init, +// .initiator_init = rc522_initiator_init, // MFRC522 has no secure element .initiator_init_secure_element = NULL, - .initiator_select_passive_target = rc522_initiator_select_passive_target, - .initiator_poll_target = rc522_initiator_poll_target, +// .initiator_select_passive_target = rc522_initiator_select_passive_target, +// .initiator_poll_target = rc522_initiator_poll_target, .initiator_select_dep_target = NULL, - .initiator_deselect_target = rc522_initiator_deselect_target, - .initiator_transceive_bytes = rc522_initiator_transceive_bytes, - .initiator_transceive_bits = rc522_initiator_transceive_bits, - .initiator_transceive_bytes_timed = rc522_initiator_transceive_bytes_timed, - .initiator_transceive_bits_timed = rc522_initiator_transceive_bits_timed, - .initiator_target_is_present = rc522_initiator_target_is_present, +// .initiator_deselect_target = rc522_initiator_deselect_target, +// .initiator_transceive_bytes = rc522_initiator_transceive_bytes, +// .initiator_transceive_bits = rc522_initiator_transceive_bits, +// .initiator_transceive_bytes_timed = rc522_initiator_transceive_bytes_timed, +// .initiator_transceive_bits_timed = rc522_initiator_transceive_bits_timed, +// .initiator_target_is_present = rc522_initiator_target_is_present, // MFRC522 is unable to work as target .target_init = NULL, @@ -348,10 +344,10 @@ const struct nfc_driver rc522_uart_driver = { .device_set_property_int = rc522_set_property_int, .get_supported_modulation = rc522_get_supported_modulation, .get_supported_baud_rate = rc522_get_supported_baud_rate, - .device_get_information_about = rc522_get_information_about, +// .device_get_information_about = rc522_get_information_about, - .abort_command = rc522_idle, - .idle = rc522_idle, - .powerdown = rc522_softdown, + .abort_command = rc522_abort, +// .idle = rc522_idle, + .powerdown = rc522_powerdown, }; diff --git a/libnfc/nfc.c b/libnfc/nfc.c index c774504..2cebb7d 100644 --- a/libnfc/nfc.c +++ b/libnfc/nfc.c @@ -118,6 +118,9 @@ # include "drivers/pn532_i2c.h" #endif /* DRIVER_PN532_I2C_ENABLED */ +#if defined (DRIVER_RC522_UART_ENABLED) +# include "drivers/rc522_uart.h" +#endif /* DRIVER_RC522_UART_ENABLED */ #define LOG_CATEGORY "libnfc.general" #define LOG_GROUP NFC_LOG_GROUP_GENERAL @@ -156,6 +159,9 @@ nfc_drivers_init(void) #if defined (DRIVER_ARYGON_ENABLED) nfc_register_driver(&arygon_driver); #endif /* DRIVER_ARYGON_ENABLED */ +#if defined (DRIVER_RC522_UART_ENABLED) + nfc_register_driver(&rc522_uart_driver); +#endif /* DRIVER_RC522_UART_ENABLED */ } static int diff --git a/libnfc/timing.c b/libnfc/timing.c new file mode 100644 index 0000000..8b17d71 --- /dev/null +++ b/libnfc/timing.c @@ -0,0 +1,75 @@ +/*- + * Free/Libre Near Field Communication (NFC) library + * + * 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 + */ + +#include "timing.h" +#include + +#define MILLIS_PER_SEC 1000 +#define MICROS_PER_SEC 1000000 +#define NANOS_PER_SEC 1000000000 + +// Use Windows' API directly if Win32 for highest possible resolution +#if defined(CYGWIN) || defined(_WIN32) +# include + + static uint64_t timer_hz = 0; + + ms_t time_millis() { + if (!timer_hz) { + // Windows API states that the frequency is fixed at boot and therefore can be cached safely + assert(QueryPerformanceFrequency((LARGE_INTEGER *) &timer_hz)); + } + + uint64_t ticks; + assert(QueryPerformanceCounter((LARGE_INTEGER *) &ticks)); + return MILLIS_PER_SEC * ticks / timer_hz; + } + +// If not Windows use POSIX methods +#else +# include + + ms_t time_millis() { + struct timespec ts; + + // CLOCK_MONOTONIC_RAW isn't affected by NTP updates and therefore really monotonic, but it's Linux-specific +# ifdef CLOCK_MONOTONIC_RAW + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); +# else + clock_gettime(CLOCK_MONOTONIC, &ts); +# endif + + return (ms_t) ts.tv_sec * MILLIS_PER_SEC + ts.tv_nsec / (NANOS_PER_SEC / MILLIS_PER_SEC); + } +#endif + +void timeout_init(timeout_t * to, unsigned int millis) { + *to = time_millis() + millis; +} + +bool timeout_check(timeout_t * to) { + if (*to == 0) { + return false; + } + + ms_t now = time_millis(); + if (now >= *to) { + *to = 0; + } + + return true; +} diff --git a/libnfc/timing.h b/libnfc/timing.h new file mode 100644 index 0000000..beb9e47 --- /dev/null +++ b/libnfc/timing.h @@ -0,0 +1,48 @@ +/*- + * Free/Libre Near Field Communication (NFC) library + * + * 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 + */ + +#ifndef __NFC_TIMING_H__ +#define __NFC_TIMING_H__ + +#include +#include + +typedef uint64_t ms_t; + +/** + * @brief Calculates a timestamp from a unspecified start with millisecond accuracy + * @return Time in milliseconds + */ +ms_t time_millis(); + +typedef ms_t timeout_t; + +/** + * @brief Initializes a timeout + * @param to Timeout handle + * @param millis Minimum expiration time in milliseconds + */ +void timeout_init(timeout_t * to, unsigned int millis); + +/** + * @brief Checks if the timeout has NOT expired + * @param to Timeout handle + * @return True if the timeout has NOT expired + */ +bool timeout_check(timeout_t * to); + +#endif