mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
2208 lines
66 KiB
C
2208 lines
66 KiB
C
/*
|
|
* Copyright (C) 2021 Freie Universität Berlin
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup drivers_mfrc522
|
|
* @brief Device driver for the MFRC522 controller
|
|
* @author Hendrik van Essen <hendrik.ve@fu-berlin.de>
|
|
* @file
|
|
* @{
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "periph/gpio.h"
|
|
#include "ztimer.h"
|
|
|
|
#include "mfrc522.h"
|
|
#include "mfrc522_regs.h"
|
|
|
|
#define MFRC522_PICC_TYPE_NAMES_ARRAY_SIZE (MFRC522_PICC_TYPE_UNKNOWN + 1)
|
|
|
|
const char mfrc522_picc_type_names[MFRC522_PICC_TYPE_NAMES_ARRAY_SIZE][50] = {
|
|
"PICC compliant with ISO/IEC 14443-4",
|
|
"PICC compliant with ISO/IEC 18092 (NFC)",
|
|
"MIFARE Mini, 320 bytes",
|
|
"MIFARE 1KB",
|
|
"MIFARE 4KB",
|
|
"MIFARE Ultralight or Ultralight C",
|
|
"MIFARE Plus",
|
|
"MIFARE DESFire",
|
|
"MIFARE TNP3XXX",
|
|
"SAK indicates UID is not complete.",
|
|
"Unknown type"
|
|
};
|
|
|
|
/**
|
|
* @brief Write n bytes to a given register of the device.
|
|
*
|
|
* @param[in] dev Device descriptor of the MFRC522
|
|
* @param[in] reg Register to write to
|
|
* @param[in] count Number of bytes to write
|
|
* @param[in] values Bytes to write
|
|
*/
|
|
static void _device_write_n(mfrc522_t *dev, mfrc522_pcd_register_t reg,
|
|
uint8_t count, const uint8_t *values)
|
|
{
|
|
assert(dev);
|
|
|
|
spi_acquire(dev->params.spi_dev, dev->params.cs_pin, SPI_MODE_0, dev->params.spi_clk);
|
|
|
|
/* LSB always 0 and address needs to be shifted left by one. (Datasheet 8.1.2.3) */
|
|
reg = reg << 1;
|
|
|
|
/* MSB == 0 is for writing. LSB is not used in address. (Datasheet 8.1.2.3) */
|
|
CLRBIT(reg, 0x80);
|
|
|
|
/* Tell MFRC522 which address we want to write */
|
|
spi_transfer_byte(dev->params.spi_dev, dev->params.cs_pin, true, reg);
|
|
|
|
for (uint8_t index = 0; index < count; index++) {
|
|
|
|
bool stop = (index == count - 1) ? false : true;
|
|
|
|
spi_transfer_byte(dev->params.spi_dev, dev->params.cs_pin, stop, values[index]);
|
|
}
|
|
|
|
spi_release(dev->params.spi_dev);
|
|
}
|
|
|
|
/**
|
|
* @brief Write single byte to a given register of the device.
|
|
*
|
|
* @param[in] dev Device descriptor of the MFRC522
|
|
* @param[in] reg Register to write to
|
|
* @param[in] value Byte to write#
|
|
*/
|
|
static void _device_write(mfrc522_t *dev,
|
|
mfrc522_pcd_register_t reg, uint8_t value)
|
|
{
|
|
assert(dev);
|
|
|
|
_device_write_n(dev, reg, 1, &value);
|
|
}
|
|
|
|
/**
|
|
* @brief Read n bytes from a given register of the device.
|
|
*
|
|
* @param[in] dev Device descriptor of the MFRC522
|
|
* @param[in] reg Register to read from
|
|
* @param[in] count Number of bytes to read
|
|
* @param[out] values Byte array to store the values in
|
|
* @param[in] rx_align Only bit positions rxAlign..7 in values[0] are updated.
|
|
*/
|
|
static void _device_read_n(mfrc522_t *dev, mfrc522_pcd_register_t reg,
|
|
uint8_t count, uint8_t *values,
|
|
uint8_t rx_align)
|
|
{
|
|
assert(dev);
|
|
|
|
if (count == 0) {
|
|
return;
|
|
}
|
|
|
|
/* last read operation is done outside the loop */
|
|
count -= 1;
|
|
|
|
spi_acquire(dev->params.spi_dev, dev->params.cs_pin, SPI_MODE_0, dev->params.spi_clk);
|
|
|
|
/* LSB always 0 and address needs to be shifted left by one. (Datasheet 8.1.2.3) */
|
|
reg = reg << 1;
|
|
|
|
/* MSB == 1 is for reading. LSB is not used in address. (Datasheet 8.1.2.3) */
|
|
SETBIT(reg, 0x80);
|
|
|
|
/* Tell MFRC522 which address we want to read */
|
|
spi_transfer_byte(dev->params.spi_dev, dev->params.cs_pin, true, reg);
|
|
|
|
/* Index in values array */
|
|
uint8_t index = 0;
|
|
|
|
/* Only update bit positions rx_align..7 in values[0] */
|
|
if (rx_align) {
|
|
/* Create bit mask for bit positions rx_align..7 */
|
|
uint8_t mask = (0xFF << rx_align) & 0xFF;
|
|
|
|
/* Read value and tell that we want to read the same address again */
|
|
uint8_t value = spi_transfer_byte(dev->params.spi_dev, dev->params.cs_pin, true, reg);
|
|
|
|
/* Apply mask to both current value of values[0] and the new data in value */
|
|
values[0] = (values[0] & ~mask) | (value & mask);
|
|
index++;
|
|
}
|
|
|
|
while (index < count) {
|
|
/* Read value and tell that we want to read the same address again */
|
|
values[index] = spi_transfer_byte(dev->params.spi_dev, dev->params.cs_pin, true, reg);
|
|
index++;
|
|
}
|
|
|
|
/* Read the final byte. Send 0 to stop reading */
|
|
values[count] = spi_transfer_byte(dev->params.spi_dev, dev->params.cs_pin, false, 0);
|
|
|
|
spi_release(dev->params.spi_dev);
|
|
}
|
|
|
|
/**
|
|
* @brief Read single byte from a given register of the device.
|
|
*
|
|
* @param[in] dev Device descriptor of the MFRC522
|
|
* @param[in] reg Register to read from
|
|
* @param[in] byte uint8_t pointer to store the value in
|
|
*/
|
|
static void _device_read(mfrc522_t *dev, mfrc522_pcd_register_t reg,
|
|
uint8_t *value)
|
|
{
|
|
assert(dev);
|
|
|
|
_device_read_n(dev, reg, 1, value, 0);
|
|
}
|
|
|
|
/**
|
|
* @brief Helper function for the two-step MIFARE Classic protocol operations
|
|
* Decrement, Increment and Restore
|
|
*
|
|
* @param[in] dev Device descriptor of the MFRC522
|
|
* @param[in] command Command to execute - MFRC522_PICC_CMD_MF_INCREMENT,
|
|
* MFRC522_PICC_CMD_MF_DECREMENT
|
|
* or MFRC522_PICC_CMD_MF_RESTORE
|
|
* @param[in] block_addr Block (0-0xff) number
|
|
* @param[in] data Data to transfer in step 2
|
|
*
|
|
* @retval 0 on success
|
|
* @retval -EINVAL if command was not out of [MFRC522_PICC_CMD_MF_INCREMENT,
|
|
* MFRC522_PICC_CMD_MF_DECREMENT, MFRC522_PICC_CMD_MF_RESTORE]
|
|
*/
|
|
static int _mifare_two_step_helper(mfrc522_t *dev,
|
|
mfrc522_picc_command_t command,
|
|
uint8_t block_addr, int32_t data)
|
|
{
|
|
assert(dev);
|
|
|
|
if ( command != MFRC522_PICC_CMD_MF_INCREMENT
|
|
&& command != MFRC522_PICC_CMD_MF_DECREMENT
|
|
&& command != MFRC522_PICC_CMD_MF_RESTORE) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
int rc;
|
|
|
|
/* We only need room for 2 bytes */
|
|
uint8_t cmd_buffer[2];
|
|
|
|
/* Step 1: Tell the PICC the command and block address */
|
|
cmd_buffer[0] = command;
|
|
cmd_buffer[1] = block_addr;
|
|
|
|
/* Adds CRC_A and checks that the response is MFRC522_MF_ACK */
|
|
rc = mfrc522_pcd_mifare_transceive(dev, cmd_buffer, 2, false);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
/* Step 2: Transfer the data */
|
|
/* Adds CRC_A and accept timeout as success */
|
|
rc = mfrc522_pcd_mifare_transceive(dev, (uint8_t *)&data, 4, true);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void mfrc522_pcd_set_register_bitmask(mfrc522_t *dev,
|
|
mfrc522_pcd_register_t reg, uint8_t mask)
|
|
{
|
|
assert(dev);
|
|
|
|
uint8_t tmp;
|
|
_device_read(dev, reg, &tmp);
|
|
|
|
/* set bit mask */
|
|
_device_write(dev, reg, tmp | mask);
|
|
}
|
|
|
|
void mfrc522_pcd_clear_register_bitmask(mfrc522_t *dev, mfrc522_pcd_register_t reg, uint8_t mask)
|
|
{
|
|
assert(dev);
|
|
|
|
uint8_t tmp;
|
|
_device_read(dev, reg, &tmp);
|
|
|
|
/* clear bit mask */
|
|
_device_write(dev, reg, tmp & (~mask));
|
|
}
|
|
|
|
int mfrc522_pcd_calculate_crc(mfrc522_t *dev, const uint8_t *data, uint8_t length, uint8_t *result)
|
|
{
|
|
assert(dev);
|
|
|
|
/* Stop any active command */
|
|
_device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_IDLE);
|
|
|
|
/* Clear the CRCIRq interrupt request bit */
|
|
_device_write(dev, MFRC522_REG_DIV_IRQ, MFRC522_BIT_DIV_IRQ_CRC_IRQ);
|
|
|
|
/* FlushBuffer = 1, FIFO initialization */
|
|
_device_write(dev, MFRC522_REG_FIFO_LEVEL, 0x80);
|
|
|
|
/* Write data to the FIFO */
|
|
_device_write_n(dev, MFRC522_REG_FIFO_DATA, length, data);
|
|
|
|
/* Start the calculation */
|
|
_device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_CALC_CRC);
|
|
|
|
/* Wait for the CRC calculation to complete */
|
|
for (uint16_t i = 5000; i > 0; i--) {
|
|
|
|
/* 5000 * 18 us sums up to 90 ms */
|
|
ztimer_sleep(ZTIMER_USEC, 18);
|
|
|
|
uint8_t n;
|
|
_device_read(dev, MFRC522_REG_DIV_IRQ, &n);
|
|
|
|
/* CRCIRq bit set - calculation done */
|
|
if (n & MFRC522_BIT_DIV_IRQ_CRC_IRQ) {
|
|
/* Stop calculating CRC for new content in the FIFO */
|
|
_device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_IDLE);
|
|
/* Transfer the result from the registers to the result buffer */
|
|
_device_read(dev, MFRC522_REG_CRC_RESULT_LSB, &result[0]);
|
|
_device_read(dev, MFRC522_REG_CRC_RESULT_MSB, &result[1]);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* 90 ms passed and nothing happened. Communication with the MFRC522 might be down. */
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
int mfrc522_pcd_init(mfrc522_t *dev, const mfrc522_params_t *params)
|
|
{
|
|
assert(dev);
|
|
assert(params);
|
|
|
|
dev->params = *params;
|
|
|
|
/* Initialize SPI bus */
|
|
int rc = spi_init_cs(dev->params.spi_dev, dev->params.cs_pin);
|
|
if (rc < 0) {
|
|
puts("error: unable to initialize the given chip select line");
|
|
return rc;
|
|
}
|
|
|
|
bool hard_reset = false;
|
|
|
|
/* If a valid pin number has been set, pull device out of power down / reset state */
|
|
if (dev->params.rst_pin != 0) {
|
|
|
|
gpio_init(dev->params.rst_pin, GPIO_IN);
|
|
|
|
if (gpio_read(dev->params.rst_pin) == 0) {
|
|
/* The MFRC522 chip is in power down mode */
|
|
|
|
gpio_init(dev->params.rst_pin, GPIO_OUT);
|
|
gpio_write(dev->params.rst_pin, 0);
|
|
/* 8.8.1 Reset timing requirements says about 100ns. Let us be generous: 2μs */
|
|
ztimer_sleep(ZTIMER_USEC, 2);
|
|
/* Exit power down mode. This triggers a hard reset. */
|
|
gpio_write(dev->params.rst_pin, 1);
|
|
/* Section 8.8.2 in the datasheet says the oscillator start-up time
|
|
* is the start-up time of the crystal + 37,74μs. Let us be generous: 50ms */
|
|
ztimer_sleep(ZTIMER_MSEC, 50);
|
|
hard_reset = true;
|
|
}
|
|
}
|
|
|
|
if (!hard_reset) {
|
|
mfrc522_pcd_reset(dev);
|
|
}
|
|
|
|
/* Reset baud rates */
|
|
_device_write(dev, MFRC522_REG_TX_MODE, 0x00);
|
|
_device_write(dev, MFRC522_REG_RX_MODE, 0x00);
|
|
|
|
/* Reset MFRC522_REG_MOD_WIDTH */
|
|
_device_write(dev, MFRC522_REG_MOD_WIDTH, 0x26);
|
|
|
|
/* When communicating with a PICC we need a timeout if something goes wrong.
|
|
* f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo].
|
|
* TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg. */
|
|
|
|
/* TAuto=1; timer starts automatically at the end of the transmission in all communication modes at all speeds */
|
|
_device_write(dev, MFRC522_REG_T_MODE, 0x80);
|
|
|
|
/* TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer =
|
|
* 40kHz, i.e. a timer period of 25μs. */
|
|
_device_write(dev, MFRC522_REG_T_PRESCALER, 0xA9);
|
|
|
|
/* Reload timer with 0x3E8 = 1000, i.e. 25ms before timeout */
|
|
_device_write(dev, MFRC522_REG_T_RELOAD_MSB, 0x03);
|
|
_device_write(dev, MFRC522_REG_T_RELOAD_LSB, 0xE8);
|
|
|
|
/* Default 0x00. Force a 100 % ASK modulation independent of the
|
|
* MFRC522_REG_MOD_GS_P register setting */
|
|
_device_write(dev, MFRC522_REG_TX_ASK, 0x40);
|
|
|
|
/* Default 0x3F. Set the preset value for the CRC coprocessor for the
|
|
* MFRC522_CMD_CALC_CRC command to 0x6363 (ISO 14443-3 part 6.2.4) */
|
|
_device_write(dev, MFRC522_REG_MODE, 0x3D);
|
|
|
|
/* Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) */
|
|
mfrc522_pcd_antenna_on(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void mfrc522_pcd_reset(mfrc522_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
_device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_SOFT_RESET);
|
|
|
|
/* The datasheet does not mention how long the SoftRest command takes to
|
|
* complete. But the MFRC522 might have been in soft power-down mode
|
|
* (triggered by bit 4 of CommandReg) Section 8.8.2 in the datasheet says
|
|
* the oscillator start-up time is the start-up time of the crystal + 37,74μs.
|
|
* Let us be generous: 50ms. */
|
|
|
|
uint8_t count = 0;
|
|
uint8_t value;
|
|
do {
|
|
/* Wait for the PowerDown bit in CommandReg to be cleared (max 3x50ms) */
|
|
ztimer_sleep(ZTIMER_MSEC, 50);
|
|
_device_read(dev, MFRC522_REG_COMMAND, &value);
|
|
} while ((value & MFRC522_BIT_COMMAND_POWER_DOWN) && (++count) < 3);
|
|
}
|
|
|
|
void mfrc522_pcd_antenna_on(mfrc522_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
uint8_t value;
|
|
_device_read(dev, MFRC522_REG_TX_CONTROL, &value);
|
|
|
|
uint8_t tx_enabled_mask =
|
|
MFRC522_BIT_TX_CONTROL_TX1_RF_EN | MFRC522_BIT_TX_CONTROL_TX2_RF_EN;
|
|
|
|
if ((value & tx_enabled_mask) != tx_enabled_mask) {
|
|
_device_write(dev, MFRC522_REG_TX_CONTROL, value | tx_enabled_mask);
|
|
}
|
|
}
|
|
|
|
void mfrc522_pcd_antenna_off(mfrc522_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
uint8_t tx_enabled_mask =
|
|
MFRC522_BIT_TX_CONTROL_TX1_RF_EN | MFRC522_BIT_TX_CONTROL_TX2_RF_EN;
|
|
|
|
mfrc522_pcd_clear_register_bitmask(dev, MFRC522_REG_TX_CONTROL, tx_enabled_mask);
|
|
}
|
|
|
|
mfrc522_pcd_rx_gain_t mfrc522_pcd_get_antenna_gain(mfrc522_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
/* See 9.3.3.6 / table 98
|
|
* Return value scrubbed with MFRC522_BITMASK_RF_CFG_RX_GAIN
|
|
* MFRC522_REG_RF_CFG may use reserved bits. */
|
|
|
|
uint8_t reg_content;
|
|
_device_read(dev, MFRC522_REG_RF_CFG, ®_content);
|
|
mfrc522_pcd_rx_gain_t gain = (reg_content & MFRC522_BITMASK_RF_CFG_RX_GAIN) >> 4;
|
|
|
|
return gain;
|
|
}
|
|
|
|
void mfrc522_pcd_set_antenna_gain(mfrc522_t *dev, mfrc522_pcd_rx_gain_t rx_gain)
|
|
{
|
|
assert(dev);
|
|
|
|
if (mfrc522_pcd_get_antenna_gain(dev) != rx_gain) {
|
|
|
|
/* See 9.3.3.6 / table 98
|
|
* Given rx_gain is scrubbed with MFRC522_BITMASK_RF_CFG_RX_GAIN
|
|
* MFRC522_REG_RF_CFG may use reserved bits.*/
|
|
|
|
mfrc522_pcd_clear_register_bitmask(dev, MFRC522_REG_RF_CFG, MFRC522_BITMASK_RF_CFG_RX_GAIN);
|
|
mfrc522_pcd_set_register_bitmask(dev, MFRC522_REG_RF_CFG,
|
|
(rx_gain << 4) & MFRC522_BITMASK_RF_CFG_RX_GAIN);
|
|
}
|
|
}
|
|
|
|
void mfrc522_pcd_soft_power_down(mfrc522_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
uint8_t value;
|
|
_device_read(dev, MFRC522_REG_COMMAND, &value);
|
|
|
|
/* set PowerDown bit to 1 */
|
|
value |= MFRC522_BIT_COMMAND_POWER_DOWN;
|
|
_device_write(dev, MFRC522_REG_COMMAND, value);
|
|
}
|
|
|
|
void mfrc522_pcd_soft_power_up(mfrc522_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
uint8_t value;
|
|
_device_read(dev, MFRC522_REG_COMMAND, &value);
|
|
|
|
/* set PowerDown bit to 0 */
|
|
value &= ~MFRC522_BIT_COMMAND_POWER_DOWN;
|
|
_device_write(dev, MFRC522_REG_COMMAND, value);
|
|
|
|
/* wait until PowerDown bit is cleared (this indicates end of wake-up procedure) */
|
|
|
|
/* create timer for timeout within 500 ms (just in case) */
|
|
const uint64_t timeout = ztimer_now(ZTIMER_MSEC) + 500;
|
|
|
|
while (ztimer_now(ZTIMER_MSEC) <= timeout) {
|
|
|
|
_device_read(dev, MFRC522_REG_COMMAND, &value);
|
|
|
|
/* if powerdown bit is 0 */
|
|
if (!(value & MFRC522_BIT_COMMAND_POWER_DOWN)) {
|
|
/* wake up procedure is finished */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int mfrc522_pcd_transceive_data(mfrc522_t *dev,
|
|
const uint8_t *send_data, uint8_t send_len,
|
|
uint8_t *back_data, uint8_t *back_len,
|
|
uint8_t *valid_bits, uint8_t rx_align, bool check_crc)
|
|
{
|
|
assert(dev);
|
|
|
|
uint8_t wait_irq = MFRC522_BIT_COM_IRQ_RX_IRQ | MFRC522_BIT_COM_IRQ_IDLE_IRQ;
|
|
|
|
return mfrc522_pcd_communicate_with_picc(dev, MFRC522_CMD_TRANSCEIVE, wait_irq,
|
|
send_data, send_len,
|
|
back_data, back_len,
|
|
valid_bits, rx_align, check_crc);
|
|
}
|
|
|
|
int mfrc522_pcd_communicate_with_picc(mfrc522_t *dev, mfrc522_pcd_command_t command,
|
|
uint8_t wait_irq,
|
|
const uint8_t *send_data, uint8_t send_len,
|
|
uint8_t *back_data, uint8_t *back_len,
|
|
uint8_t *valid_bits, uint8_t rx_align, bool check_crc)
|
|
{
|
|
assert(dev);
|
|
|
|
/* prepare values for BitFramingReg */
|
|
uint8_t tx_last_bits = valid_bits ? *valid_bits : 0;
|
|
|
|
/* RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] */
|
|
uint8_t bit_framing = (rx_align << 4) + tx_last_bits;
|
|
|
|
/* stop any active command */
|
|
_device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_IDLE);
|
|
|
|
/* clear all seven interrupt request bits */
|
|
uint8_t irq_bits = MFRC522_BIT_COM_IRQ_TIMER_IRQ
|
|
| MFRC522_BIT_COM_IRQ_ERR_IRQ
|
|
| MFRC522_BIT_COM_IRQ_LO_ALERT_IRQ
|
|
| MFRC522_BIT_COM_IRQ_HI_ALERT_IRQ
|
|
| MFRC522_BIT_COM_IRQ_IDLE_IRQ
|
|
| MFRC522_BIT_COM_IRQ_RX_IRQ
|
|
| MFRC522_BIT_COM_IRQ_TX_IRQ;
|
|
|
|
_device_write(dev, MFRC522_REG_COM_IRQ, irq_bits);
|
|
|
|
/* FlushBuffer = 1, FIFO initialization */
|
|
_device_write(dev, MFRC522_REG_FIFO_LEVEL, 0x80);
|
|
|
|
/* write send_data to the FIFO */
|
|
_device_write_n(dev, MFRC522_REG_FIFO_DATA, send_len, send_data);
|
|
|
|
/* bit adjustments */
|
|
_device_write(dev, MFRC522_REG_BIT_FRAMING, bit_framing);
|
|
|
|
/* execute the command */
|
|
_device_write(dev, MFRC522_REG_COMMAND, command);
|
|
|
|
if (command == MFRC522_CMD_TRANSCEIVE) {
|
|
/* StartSend=1, transmission of data starts */
|
|
mfrc522_pcd_set_register_bitmask(dev, MFRC522_REG_BIT_FRAMING, 0x80);
|
|
}
|
|
|
|
/* Wait for the command to complete. In mfrc522_pcd_init() we set the TAuto
|
|
* flag in TModeReg. This means the timer automatically starts when the PCD
|
|
* stops transmitting. */
|
|
uint16_t i;
|
|
for (i = 2000; i > 0; i--) {
|
|
|
|
/* 2000 * 18 us sums up to 36 ms */
|
|
ztimer_sleep(ZTIMER_USEC, 18);
|
|
|
|
uint8_t n;
|
|
_device_read(dev, MFRC522_REG_COM_IRQ, &n);
|
|
|
|
/* One of the interrupts that signals success has been set */
|
|
if (n & wait_irq) {
|
|
break;
|
|
}
|
|
|
|
/* Timer interrupt - nothing received in 25ms */
|
|
if (n & MFRC522_BIT_COM_IRQ_TIMER_IRQ) {
|
|
return -ETIMEDOUT;
|
|
}
|
|
}
|
|
|
|
/* 36ms and nothing happened. Communication with the MFRC522 might be down. */
|
|
if (i == 0) {
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
uint8_t error_reg_value;
|
|
_device_read(dev, MFRC522_REG_ERROR, &error_reg_value);
|
|
|
|
uint8_t error_mask = MFRC522_BIT_ERROR_BUFFER_OVFL
|
|
| MFRC522_BIT_ERROR_PARITY_ERR
|
|
| MFRC522_BIT_ERROR_PROTOCOL_ERR;
|
|
|
|
/* Stop now if any errors except collisions were detected */
|
|
if (error_reg_value & error_mask) {
|
|
return -EIO;
|
|
}
|
|
|
|
uint8_t _valid_bits = 0;
|
|
|
|
/* If the caller wants data back, get it from the MFRC522 */
|
|
if (back_data && back_len) {
|
|
/* Number of bytes in the FIFO */
|
|
uint8_t n;
|
|
_device_read(dev, MFRC522_REG_FIFO_LEVEL, &n);
|
|
if (n > *back_len) {
|
|
return -ENOBUFS;
|
|
}
|
|
/* Number of bytes returned */
|
|
*back_len = n;
|
|
|
|
/* Get received data from FIFO */
|
|
_device_read_n(dev, MFRC522_REG_FIFO_DATA, n, back_data, rx_align);
|
|
|
|
/* RxLastBits[2:0] indicates the number of valid bits in the last
|
|
* received byte. If this value is 000b, the whole byte is valid. */
|
|
uint8_t tmp;
|
|
_device_read(dev, MFRC522_REG_CONTROL, &tmp);
|
|
_valid_bits = tmp & MFRC522_BITMASK_CONTROL_RX_LAST_BITS;
|
|
|
|
if (valid_bits) {
|
|
*valid_bits = _valid_bits;
|
|
}
|
|
}
|
|
|
|
if (error_reg_value & MFRC522_BIT_ERROR_COLL_ERR) {
|
|
return -ECONNABORTED;
|
|
}
|
|
|
|
/* Perform CRC_A validation if requested */
|
|
if (back_data && back_len && check_crc) {
|
|
|
|
/* In this case a MIFARE Classic NAK is not OK */
|
|
if (*back_len == 1 && _valid_bits == 4) {
|
|
return -EIO;
|
|
}
|
|
|
|
/* We need at least the CRC_A value and all 8 bits of the last byte must
|
|
* be received */
|
|
if (*back_len < 2 || _valid_bits != 0) {
|
|
return -EIO;
|
|
}
|
|
|
|
/* Verify CRC_A - do our own calculation and store the control in
|
|
* control_buffer */
|
|
uint8_t control_buffer[2];
|
|
|
|
int rc = mfrc522_pcd_calculate_crc(dev, &back_data[0], *back_len - 2, &control_buffer[0]);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
if ((back_data[*back_len - 2] != control_buffer[0]) ||
|
|
(back_data[*back_len - 1] != control_buffer[1])) {
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mfrc522_picc_request_a(mfrc522_t *dev, uint8_t *buffer_atqa, uint8_t *buffer_size)
|
|
{
|
|
assert(dev);
|
|
|
|
return mfrc522_picc_reqa_or_wupa(
|
|
dev, MFRC522_PICC_CMD_ISO_14443_REQA, buffer_atqa, buffer_size);
|
|
}
|
|
|
|
int mfrc522_picc_wakeup_a(mfrc522_t *dev, uint8_t *buffer_atqa, uint8_t *buffer_size)
|
|
{
|
|
assert(dev);
|
|
|
|
return mfrc522_picc_reqa_or_wupa(
|
|
dev, MFRC522_PICC_CMD_ISO_14443_WUPA, buffer_atqa, buffer_size);
|
|
}
|
|
|
|
int mfrc522_picc_reqa_or_wupa(mfrc522_t *dev, mfrc522_picc_command_t command,
|
|
uint8_t *buffer_atqa, uint8_t *buffer_size)
|
|
{
|
|
assert(dev);
|
|
|
|
uint8_t valid_bits;
|
|
int rc;
|
|
|
|
if ( command != MFRC522_PICC_CMD_ISO_14443_REQA
|
|
&& command != MFRC522_PICC_CMD_ISO_14443_WUPA) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* The ATQA response is 2 bytes long */
|
|
if (buffer_atqa == NULL || *buffer_size < 2) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
/* Bits received after collision are cleared */
|
|
mfrc522_pcd_clear_register_bitmask(
|
|
dev, MFRC522_REG_COLL, MFRC522_BIT_COLL_VALUES_AFTER_COLL);
|
|
|
|
/* For REQA and WUPA we need the short frame format - transmit only 7 bits
|
|
* of the last (and only) byte. TxLastBits = BitFramingReg[2..0] */
|
|
valid_bits = 7;
|
|
|
|
rc = mfrc522_pcd_transceive_data(dev, (uint8_t *)&command, 1, buffer_atqa, buffer_size,
|
|
&valid_bits, 0, false);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
/* ATQA must be exactly 16 bits */
|
|
if (*buffer_size != 2 || valid_bits != 0) {
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mfrc522_picc_select(mfrc522_t *dev, mfrc522_uid_t *uid, uint8_t valid_bits)
|
|
{
|
|
assert(dev);
|
|
|
|
int rc;
|
|
bool uid_complete;
|
|
bool use_cascade_tag;
|
|
uint8_t cascade_level = 1;
|
|
uint8_t count;
|
|
uint8_t check_bit;
|
|
|
|
/* First index in uid->uid_byte[] that is used in the current Cascade Level */
|
|
uint8_t uid_index;
|
|
|
|
/* SELECT/ANTICOLLISION commands uses a 7 byte standard frame + 2 bytes CRC_A */
|
|
uint8_t buffer[9];
|
|
|
|
/* Number of bytes used in the buffer, i.e. the number of bytes to transfer to the FIFO */
|
|
uint8_t buffer_used;
|
|
|
|
/* Used in BitFramingReg. Defines the bit position for the first bit received */
|
|
uint8_t rx_align;
|
|
|
|
/* Used in BitFramingReg. The number of valid bits in the last transmitted byte */
|
|
uint8_t tx_last_bits;
|
|
uint8_t *response_buffer;
|
|
uint8_t response_length;
|
|
|
|
/* Description of buffer structure:
|
|
* Byte 0: SEL Indicates the Cascade Level:
|
|
* MFRC522_PICC_CMD_ISO_14443_SEL_CL1, MFRC522_PICC_CMD_ISO_14443_SEL_CL2
|
|
* or MFRC522_PICC_CMD_ISO_14443_SEL_CL3
|
|
* Byte 1: NVBNumber of Valid Bits (in complete command, not just the UID):
|
|
* High nibble: complete bytes, Low nibble: Extra bits.
|
|
* Byte 2: UID-data or CTSee explanation below. CT means Cascade Tag.
|
|
* Byte 3: UID-data
|
|
* Byte 4: UID-data
|
|
* Byte 5: UID-data
|
|
* Byte 6: BCCBlock Check Character - XOR of bytes 2-5
|
|
* Byte 7: CRC_A
|
|
* Byte 8: CRC_A
|
|
*
|
|
* The BCC and CRC_A are only transmitted if we know all the UID bits of the
|
|
* current Cascade Level.
|
|
*
|
|
* Description of bytes 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft:
|
|
* UID contents and cascade levels)
|
|
* Description of bytes 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft:
|
|
* UID contents and cascade levels)
|
|
*
|
|
* UID size Cascade level Byte2 Byte3 Byte4 Byte5
|
|
* ======== ============= ===== ===== ===== =====
|
|
* 4 bytes 1 uid0 uid1 uid2 uid3
|
|
* 7 bytes 1 CT uid0 uid1 uid2
|
|
* 2 uid3 uid4 uid5 uid6
|
|
* 10 bytes 1 CT uid0 uid1 uid2
|
|
* 2 CT uid3 uid4 uid5
|
|
* 3 uid6 uid7 uid8 uid9
|
|
*/
|
|
|
|
/* Sanity check */
|
|
if (valid_bits > 80) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Bits received after collision are cleared */
|
|
mfrc522_pcd_clear_register_bitmask(
|
|
dev, MFRC522_REG_COLL, MFRC522_BIT_COLL_VALUES_AFTER_COLL);
|
|
|
|
/* Repeat Cascade Level loop until we have a complete UID */
|
|
uid_complete = false;
|
|
while (!uid_complete) {
|
|
/* Set the Cascade Level in the SEL byte, find out if we need to use the
|
|
* Cascade Tag in byte 2 */
|
|
switch (cascade_level) {
|
|
|
|
case 1:
|
|
buffer[0] = MFRC522_PICC_CMD_ISO_14443_SEL_CL1;
|
|
uid_index = 0;
|
|
|
|
/* When we know that the UID has more than 4 bytes */
|
|
use_cascade_tag = valid_bits && uid->size > 4;
|
|
break;
|
|
|
|
case 2:
|
|
buffer[0] = MFRC522_PICC_CMD_ISO_14443_SEL_CL2;
|
|
uid_index = 3;
|
|
|
|
/* When we know that the UID has more than 7 bytes */
|
|
use_cascade_tag = valid_bits && uid->size > 7;
|
|
break;
|
|
|
|
case 3:
|
|
buffer[0] = MFRC522_PICC_CMD_ISO_14443_SEL_CL3;
|
|
uid_index = 6;
|
|
|
|
/* Never used in CL3 */
|
|
use_cascade_tag = false;
|
|
break;
|
|
|
|
default:
|
|
return -ECANCELED;
|
|
break;
|
|
}
|
|
|
|
/* Number of known UID bits in the current Cascade Level */
|
|
int8_t current_level_known_bits;
|
|
|
|
/* How many UID bits are known in this Cascade Level? */
|
|
current_level_known_bits = valid_bits - (8 * uid_index);
|
|
if (current_level_known_bits < 0) {
|
|
current_level_known_bits = 0;
|
|
}
|
|
|
|
|
|
/* destination index in buffer[] */
|
|
uint8_t index = 2;
|
|
if (use_cascade_tag) {
|
|
buffer[index++] = MFRC522_PICC_CASCADE_TAG;
|
|
}
|
|
|
|
/* Number of bytes needed to represent the known bits for this level */
|
|
uint8_t bytes_to_copy =
|
|
current_level_known_bits / 8 + ((current_level_known_bits % 8) ? 1 : 0);
|
|
|
|
if (bytes_to_copy) {
|
|
/* Max 4 bytes in each Cascade Level. Only 3 left if we use the Cascade Tag */
|
|
uint8_t max_bytes = use_cascade_tag ? 3 : 4;
|
|
|
|
if (bytes_to_copy > max_bytes) {
|
|
bytes_to_copy = max_bytes;
|
|
}
|
|
|
|
for (count = 0; count < bytes_to_copy; count++) {
|
|
buffer[index++] = uid->uid_byte[uid_index + count];
|
|
}
|
|
}
|
|
|
|
/* Now that the data has been copied we need to include the 8 bits in CT
|
|
* in current_level_known_bits */
|
|
if (use_cascade_tag) {
|
|
current_level_known_bits += 8;
|
|
}
|
|
|
|
/* Repeat anti collision loop until we can transmit all UID bits + BCC
|
|
* and receive a SAK - max 32 iterations */
|
|
bool select_done = false;
|
|
while (!select_done) {
|
|
/* Find out how many bits and bytes to send and receive */
|
|
if (current_level_known_bits >= 32) {
|
|
/* All UID bits in this Cascade Level are known. This is a SELECT. */
|
|
|
|
/* NVB - Number of Valid Bits: Seven whole bytes */
|
|
buffer[1] = 0x70;
|
|
|
|
/* Calculate BCC - Block Check Character */
|
|
buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5];
|
|
|
|
/* Calculate CRC_A */
|
|
rc = mfrc522_pcd_calculate_crc(dev, buffer, 7, &buffer[7]);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
/* 0 => All 8 bits are valid */
|
|
tx_last_bits = 0;
|
|
buffer_used = 9;
|
|
|
|
/* Store response in the last 3 bytes of buffer (BCC and CRC_A -
|
|
* not needed after tx) */
|
|
response_buffer = &buffer[6];
|
|
response_length = 3;
|
|
}
|
|
else {
|
|
/* This is an ANTICOLLISION */
|
|
|
|
tx_last_bits = current_level_known_bits % 8;
|
|
|
|
/* Number of whole bytes in the UID part */
|
|
count = current_level_known_bits / 8;
|
|
|
|
/* Number of whole bytes: SEL + NVB + UIDs */
|
|
index = 2 + count;
|
|
|
|
/* NVB - Number of Valid Bits */
|
|
buffer[1] = (index << 4) + tx_last_bits;
|
|
buffer_used = index + (tx_last_bits ? 1 : 0);
|
|
|
|
/* Store response in the unused part of buffer */
|
|
response_buffer = &buffer[index];
|
|
response_length = sizeof(buffer) - index;
|
|
}
|
|
|
|
rx_align = tx_last_bits;
|
|
/* RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] */
|
|
_device_write(dev, MFRC522_REG_BIT_FRAMING, (rx_align << 4) + tx_last_bits);
|
|
|
|
/* Transmit the buffer and receive the response */
|
|
rc = mfrc522_pcd_transceive_data(dev, buffer, buffer_used,
|
|
response_buffer, &response_length,
|
|
&tx_last_bits, rx_align, false);
|
|
|
|
if (rc == -ECONNABORTED) {
|
|
/* More than one PICC in the field => collision */
|
|
|
|
uint8_t value_of_coll_reg;
|
|
_device_read(dev, MFRC522_REG_COLL, &value_of_coll_reg);
|
|
|
|
if (value_of_coll_reg & MFRC522_BIT_COLL_COLL_POS_NOT_VALID) {
|
|
/* Without a valid collision position we cannot continue */
|
|
return -ECONNABORTED;
|
|
}
|
|
|
|
/* Values 0-31, 0 means bit 32 */
|
|
uint8_t collision_pos = value_of_coll_reg & 0x1F;
|
|
if (collision_pos == 0) {
|
|
collision_pos = 32;
|
|
}
|
|
|
|
/* No progress - should not happen */
|
|
if (collision_pos <= current_level_known_bits) {
|
|
return -ECANCELED;
|
|
}
|
|
|
|
/* Choose the PICC with the bit set */
|
|
current_level_known_bits = collision_pos;
|
|
|
|
/* The bit to modify */
|
|
count = current_level_known_bits % 8;
|
|
check_bit = (current_level_known_bits - 1) % 8;
|
|
|
|
/* First byte is index 0 */
|
|
index = 1 + (current_level_known_bits / 8) + (count ? 1 : 0);
|
|
buffer[index] |= (1 << check_bit);
|
|
}
|
|
else if (rc != 0) {
|
|
return rc;
|
|
}
|
|
else {
|
|
/* SUCCESS */
|
|
|
|
if (current_level_known_bits >= 32) {
|
|
/* This was a SELECT */
|
|
|
|
/* No more anticollision */
|
|
select_done = true;
|
|
/* We continue below outside the while */
|
|
}
|
|
else {
|
|
/* This was an ANTICOLLISION */
|
|
|
|
/* We now have all 32 bits of the UID in this Cascade Level */
|
|
current_level_known_bits = 32;
|
|
/* Run loop again to do the SELECT */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We do not check the CBB - it was constructed by us above */
|
|
|
|
index = (buffer[2] == MFRC522_PICC_CASCADE_TAG) ? 3 : 2;
|
|
bytes_to_copy = (buffer[2] == MFRC522_PICC_CASCADE_TAG) ? 3 : 4;
|
|
for (count = 0; count < bytes_to_copy; count++) {
|
|
uid->uid_byte[uid_index + count] = buffer[index++];
|
|
}
|
|
|
|
/* SAK must be exactly 24 bits (1 byte + CRC_A) */
|
|
if (response_length != 3 || tx_last_bits != 0) {
|
|
return -EIO;
|
|
}
|
|
|
|
/* Verify CRC_A - do our own calculation and store the control in
|
|
* buffer[2..3] - those bytes are not needed anymore */
|
|
rc = mfrc522_pcd_calculate_crc(dev, response_buffer, 1, &buffer[2]);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
if ((buffer[2] != response_buffer[1]) || (buffer[3] != response_buffer[2])) {
|
|
return -EIO;
|
|
}
|
|
|
|
/* Cascade bit set - UID not complete yes*/
|
|
if (response_buffer[0] & 0x04) {
|
|
cascade_level++;
|
|
}
|
|
else {
|
|
uid_complete = true;
|
|
uid->sak = response_buffer[0];
|
|
}
|
|
}
|
|
|
|
/* Set correct uid->size */
|
|
uid->size = 3 * cascade_level + 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mfrc522_picc_halt_a(mfrc522_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
int rc;
|
|
uint8_t buffer[4];
|
|
|
|
/* Build command buffer */
|
|
buffer[0] = MFRC522_PICC_CMD_ISO_14443_HLTA;
|
|
buffer[1] = 0;
|
|
|
|
/* Calculate CRC_A */
|
|
rc = mfrc522_pcd_calculate_crc(dev, buffer, 2, &buffer[2]);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
/* Send the command. The standard says: If the PICC responds with any
|
|
* modulation during a period of 1 ms after the end of the frame containing
|
|
* the HLTA command, this response shall be interpreted as 'not acknowledge'.
|
|
* We interpret that this way: Only -ETIMEDOUT is a success. */
|
|
rc = mfrc522_pcd_transceive_data(dev, buffer, sizeof(buffer), NULL, NULL, NULL, 0, false);
|
|
if (rc == -ETIMEDOUT) {
|
|
return 0;
|
|
}
|
|
|
|
/* That is ironically NOT ok in this case ;-) */
|
|
if (rc == 0) {
|
|
return -EIO;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int mfrc522_pcd_authenticate(mfrc522_t *dev, mfrc522_picc_command_t command,
|
|
uint8_t block_addr, const mfrc522_mifare_key_t *key,
|
|
const mfrc522_uid_t *uid)
|
|
{
|
|
assert(dev);
|
|
|
|
uint8_t wait_irq = MFRC522_BIT_COM_IRQ_IDLE_IRQ;
|
|
|
|
uint8_t send_data[12];
|
|
send_data[0] = command;
|
|
send_data[1] = block_addr;
|
|
|
|
/* 6 key bytes */
|
|
for (uint8_t i = 0; i < MFRC522_MF_KEY_SIZE; i++) {
|
|
send_data[2 + i] = key->key_byte[i];
|
|
}
|
|
|
|
/* Use the last uid bytes as specified in
|
|
* http://cache.nxp.com/documents/application_note/AN10927.pdf section 3.2.5
|
|
* "MIFARE Classic Authentication". The only missed case is the MF1Sxxxx
|
|
* shortcut activation, but it requires cascade tag (CT) byte, that is not
|
|
* part of uid. */
|
|
|
|
for (uint8_t i = 0; i < 4; i++) {
|
|
/* The last 4 bytes of the UID */
|
|
send_data[8 + i] = uid->uid_byte[i + uid->size - 4];
|
|
}
|
|
|
|
/* Start the authentication */
|
|
return mfrc522_pcd_communicate_with_picc(
|
|
dev, MFRC522_CMD_MF_AUTHENT, wait_irq,
|
|
&send_data[0], sizeof(send_data), NULL, NULL, NULL, 0, false);
|
|
}
|
|
|
|
void mfrc522_pcd_stop_crypto1(mfrc522_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
mfrc522_pcd_clear_register_bitmask(
|
|
dev, MFRC522_REG_STATUS_2, MFRC522_BIT_STATUS_2_MF_CRYPTO_1_ON);
|
|
}
|
|
|
|
int mfrc522_mifare_read(mfrc522_t *dev, uint8_t block_addr, uint8_t *buffer, uint8_t *buffer_size)
|
|
{
|
|
assert(dev);
|
|
|
|
int rc;
|
|
|
|
/* Sanity check */
|
|
if (buffer == NULL || *buffer_size < 18) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
/* Build command buffer */
|
|
buffer[0] = MFRC522_PICC_CMD_MF_READ;
|
|
buffer[1] = block_addr;
|
|
|
|
/* Calculate CRC_A */
|
|
rc = mfrc522_pcd_calculate_crc(dev, buffer, 2, &buffer[2]);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
/* Transmit the buffer and receive the response, validate CRC_A */
|
|
return mfrc522_pcd_transceive_data(
|
|
dev, buffer, 4, buffer, buffer_size, NULL, 0, true);
|
|
}
|
|
|
|
int mfrc522_mifare_write(mfrc522_t *dev, uint8_t block_addr,
|
|
const uint8_t *buffer, uint8_t buffer_size)
|
|
{
|
|
assert(dev);
|
|
|
|
int rc;
|
|
|
|
/* Sanity check */
|
|
if (buffer == NULL || buffer_size < 16) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Mifare Classic protocol requires two communications to perform a write.
|
|
* Step 1: Tell the PICC we want to write to block block_addr */
|
|
uint8_t cmd_buffer[2];
|
|
cmd_buffer[0] = MFRC522_PICC_CMD_MF_WRITE;
|
|
cmd_buffer[1] = block_addr;
|
|
|
|
/* Adds CRC_A and checks that the response is MFRC522_MF_ACK */
|
|
rc = mfrc522_pcd_mifare_transceive(dev, cmd_buffer, 2, false);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
/* Step 2: Transfer the data */
|
|
/* Adds CRC_A and checks that the response is MFRC522_MF_ACK */
|
|
rc = mfrc522_pcd_mifare_transceive(dev, buffer, buffer_size, false);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mfrc522_mifare_ultralight_write(mfrc522_t *dev, uint8_t page, const uint8_t *buffer)
|
|
{
|
|
assert(dev);
|
|
|
|
int rc;
|
|
|
|
/* Sanity check */
|
|
if (buffer == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Build command buffer */
|
|
uint8_t cmd_buffer[6];
|
|
cmd_buffer[0] = MFRC522_PICC_CMD_MF_UL_WRITE;
|
|
cmd_buffer[1] = page;
|
|
memcpy(&cmd_buffer[2], buffer, 4);
|
|
|
|
/* Perform the write */
|
|
/* Adds CRC_A and checks that the response is MFRC522_MF_ACK */
|
|
rc = mfrc522_pcd_mifare_transceive(dev, cmd_buffer, 6, false);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mfrc522_mifare_decrement(mfrc522_t *dev, uint8_t block_addr, int32_t delta)
|
|
{
|
|
assert(dev);
|
|
|
|
int rc = _mifare_two_step_helper(dev, MFRC522_PICC_CMD_MF_DECREMENT, block_addr, delta);
|
|
|
|
if (rc == -EINVAL) {
|
|
return -ECANCELED;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int mfrc522_mifare_increment(mfrc522_t *dev, uint8_t block_addr, int32_t delta)
|
|
{
|
|
assert(dev);
|
|
|
|
int rc = _mifare_two_step_helper(dev, MFRC522_PICC_CMD_MF_INCREMENT, block_addr, delta);
|
|
|
|
if (rc == -EINVAL) {
|
|
return -ECANCELED;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int mfrc522_mifare_restore(mfrc522_t *dev, uint8_t block_addr)
|
|
{
|
|
assert(dev);
|
|
|
|
/* The datasheet describes Restore as a two step operation, but does not
|
|
* explain what data to transfer in step 2. Doing only a single step does
|
|
* not work, so transfer 0L in step two. */
|
|
int rc = _mifare_two_step_helper(dev, MFRC522_PICC_CMD_MF_RESTORE, block_addr, 0L);
|
|
|
|
if (rc == -EINVAL) {
|
|
return -ECANCELED;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int mfrc522_mifare_transfer(mfrc522_t *dev, uint8_t block_addr)
|
|
{
|
|
assert(dev);
|
|
|
|
int rc;
|
|
|
|
/* We only need room for 2 bytes */
|
|
uint8_t cmd_buffer[2];
|
|
|
|
/* Tell the PICC we want to transfer the result into block block_addr */
|
|
cmd_buffer[0] = MFRC522_PICC_CMD_MF_TRANSFER;
|
|
cmd_buffer[1] = block_addr;
|
|
|
|
/* Adds CRC_A and checks that the response is MFRC522_MF_ACK */
|
|
rc = mfrc522_pcd_mifare_transceive(dev, cmd_buffer, 2, false);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mfrc522_mifare_get_value(mfrc522_t *dev, uint8_t block_addr, int32_t *value)
|
|
{
|
|
assert(dev);
|
|
|
|
int rc;
|
|
uint8_t buffer[18];
|
|
uint8_t size = sizeof(buffer);
|
|
|
|
rc = mfrc522_mifare_read(dev, block_addr, buffer, &size);
|
|
|
|
if (rc == 0) {
|
|
/* Extract the value */
|
|
*value = (((int32_t)buffer[3]) << 24)
|
|
| (((int32_t)buffer[2]) << 16)
|
|
| (((int32_t)buffer[1]) << 8)
|
|
| ((int32_t)buffer[0]);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int mfrc522_mifare_set_value(mfrc522_t *dev, uint8_t block_addr, int32_t value)
|
|
{
|
|
assert(dev);
|
|
|
|
uint8_t buffer[18];
|
|
|
|
/* Translate the int32_t into 4 bytes; repeated 2x in value block */
|
|
buffer[0] = buffer[ 8] = (value & 0xFF);
|
|
buffer[1] = buffer[ 9] = (value & 0xFF00) >> 8;
|
|
buffer[2] = buffer[10] = (value & 0xFF0000) >> 16;
|
|
buffer[3] = buffer[11] = (value & 0xFF000000) >> 24;
|
|
|
|
/* Inverse 4 bytes also found in value block */
|
|
buffer[4] = ~buffer[0];
|
|
buffer[5] = ~buffer[1];
|
|
buffer[6] = ~buffer[2];
|
|
buffer[7] = ~buffer[3];
|
|
|
|
/* Address 2x with inverse address 2x */
|
|
buffer[12] = buffer[14] = block_addr;
|
|
buffer[13] = buffer[15] = ~block_addr;
|
|
|
|
/* Write the whole data block */
|
|
return mfrc522_mifare_write(dev, block_addr, buffer, 16);
|
|
}
|
|
|
|
int mfrc522_pcd_ntag216_auth(mfrc522_t *dev, const uint8_t *password, uint8_t p_ack[])
|
|
{
|
|
assert(dev);
|
|
|
|
/* TODO: Fix cmd_buffer length and rx_len. They really should match.
|
|
* (Better still, rx_len should not even be necessary.) */
|
|
|
|
int rc;
|
|
|
|
/* We need room for 16 bytes data and 2 bytes CRC_A */
|
|
uint8_t cmd_buffer[18];
|
|
|
|
/* Command for authentication */
|
|
cmd_buffer[0] = 0x1B;
|
|
|
|
for (uint8_t i = 0; i < 4; i++) {
|
|
cmd_buffer[i + 1] = password[i];
|
|
}
|
|
|
|
rc = mfrc522_pcd_calculate_crc(dev, cmd_buffer, 5, &cmd_buffer[5]);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
/* Transceive the data, store the reply in cmd_buffer[] */
|
|
/* RxIRq and IdleIRq */
|
|
uint8_t wait_irq = 0x30;
|
|
/* uint8_t cmd_buffer_size = sizeof(cmd_buffer); */
|
|
uint8_t valid_bits = 0;
|
|
uint8_t rx_len = 5;
|
|
|
|
rc = mfrc522_pcd_communicate_with_picc(dev, MFRC522_CMD_TRANSCEIVE, wait_irq, cmd_buffer, 7,
|
|
cmd_buffer, &rx_len, &valid_bits, 0, false);
|
|
|
|
p_ack[0] = cmd_buffer[0];
|
|
p_ack[1] = cmd_buffer[1];
|
|
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mfrc522_pcd_mifare_transceive(mfrc522_t *dev,
|
|
const uint8_t *send_data, uint8_t send_len,
|
|
bool accept_timeout)
|
|
{
|
|
assert(dev);
|
|
|
|
int rc;
|
|
|
|
/* We need room for 16 bytes data and 2 bytes CRC_A */
|
|
uint8_t cmd_buffer[18];
|
|
|
|
/* Sanity check */
|
|
if (send_data == NULL || send_len > 16) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Copy send_data[] to cmd_buffer[] and add CRC_A */
|
|
memcpy(cmd_buffer, send_data, send_len);
|
|
|
|
rc = mfrc522_pcd_calculate_crc(dev, cmd_buffer, send_len, &cmd_buffer[send_len]);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
send_len += 2;
|
|
|
|
/* Transceive the data, store the reply in cmd_buffer[] */
|
|
uint8_t wait_irq = MFRC522_BIT_COM_IRQ_RX_IRQ | MFRC522_BIT_COM_IRQ_IDLE_IRQ;
|
|
uint8_t cmd_buffer_size = sizeof(cmd_buffer);
|
|
uint8_t valid_bits = 0;
|
|
|
|
rc = mfrc522_pcd_communicate_with_picc(
|
|
dev, MFRC522_CMD_TRANSCEIVE, wait_irq, cmd_buffer, send_len,
|
|
cmd_buffer, &cmd_buffer_size, &valid_bits, 0, false);
|
|
|
|
if (accept_timeout && rc == -ETIMEDOUT) {
|
|
return 0;
|
|
}
|
|
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
/* The PICC must reply with a 4 bit ACK */
|
|
if (cmd_buffer_size != 1 || valid_bits != 4) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (cmd_buffer[0] != MFRC522_MF_ACK) {
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
mfrc522_picc_type_t mfrc522_picc_get_type(uint8_t sak)
|
|
{
|
|
/* http://www.nxp.com/documents/application_note/AN10833.pdf 3.2 Coding of
|
|
* Select Acknowledge (SAK): ignore 8-bit (iso14443 starts with LSBit = bit 1)
|
|
* fixes wrong type for manufacturer Infineon
|
|
* (http://nfc-tools.org/index.php?title=ISO14443A) */
|
|
|
|
sak &= 0x7F;
|
|
switch (sak) {
|
|
case 0x04:
|
|
/* UID not complete */
|
|
return MFRC522_PICC_TYPE_NOT_COMPLETE;
|
|
|
|
case 0x09:
|
|
return MFRC522_PICC_TYPE_MIFARE_MINI;
|
|
|
|
case 0x08:
|
|
return MFRC522_PICC_TYPE_MIFARE_1K;
|
|
|
|
case 0x18:
|
|
return MFRC522_PICC_TYPE_MIFARE_4K;
|
|
|
|
case 0x00:
|
|
return MFRC522_PICC_TYPE_MIFARE_UL;
|
|
|
|
case 0x10:
|
|
/* fall through */
|
|
case 0x11:
|
|
return MFRC522_PICC_TYPE_MIFARE_PLUS;
|
|
|
|
case 0x01:
|
|
return MFRC522_PICC_TYPE_TNP3XXX;
|
|
|
|
case 0x20:
|
|
return MFRC522_PICC_TYPE_ISO_14443_4;
|
|
|
|
case 0x40:
|
|
return MFRC522_PICC_TYPE_ISO_18092;
|
|
|
|
default:
|
|
return MFRC522_PICC_TYPE_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
const char *mfrc522_picc_get_type_string(mfrc522_picc_type_t picc_type)
|
|
{
|
|
switch (picc_type) {
|
|
case MFRC522_PICC_TYPE_ISO_14443_4:
|
|
return mfrc522_picc_type_names[MFRC522_PICC_TYPE_ISO_14443_4];
|
|
|
|
case MFRC522_PICC_TYPE_ISO_18092:
|
|
return mfrc522_picc_type_names[MFRC522_PICC_TYPE_ISO_18092];
|
|
|
|
case MFRC522_PICC_TYPE_MIFARE_MINI:
|
|
return mfrc522_picc_type_names[MFRC522_PICC_TYPE_MIFARE_MINI];
|
|
|
|
case MFRC522_PICC_TYPE_MIFARE_1K:
|
|
return mfrc522_picc_type_names[MFRC522_PICC_TYPE_MIFARE_1K];
|
|
|
|
case MFRC522_PICC_TYPE_MIFARE_4K:
|
|
return mfrc522_picc_type_names[MFRC522_PICC_TYPE_MIFARE_4K];
|
|
|
|
case MFRC522_PICC_TYPE_MIFARE_UL:
|
|
return mfrc522_picc_type_names[MFRC522_PICC_TYPE_MIFARE_UL];
|
|
|
|
case MFRC522_PICC_TYPE_MIFARE_PLUS:
|
|
return mfrc522_picc_type_names[MFRC522_PICC_TYPE_MIFARE_PLUS];
|
|
|
|
case MFRC522_PICC_TYPE_MIFARE_DESFIRE:
|
|
return mfrc522_picc_type_names[MFRC522_PICC_TYPE_MIFARE_DESFIRE];
|
|
|
|
case MFRC522_PICC_TYPE_TNP3XXX:
|
|
return mfrc522_picc_type_names[MFRC522_PICC_TYPE_TNP3XXX];
|
|
|
|
case MFRC522_PICC_TYPE_NOT_COMPLETE:
|
|
return mfrc522_picc_type_names[MFRC522_PICC_TYPE_NOT_COMPLETE];
|
|
|
|
case MFRC522_PICC_TYPE_UNKNOWN:
|
|
/* fall through */
|
|
default:
|
|
return mfrc522_picc_type_names[MFRC522_PICC_TYPE_UNKNOWN];
|
|
}
|
|
}
|
|
|
|
void mfrc522_mifare_set_access_bits(uint8_t *access_bit_buffer,
|
|
uint8_t g0, uint8_t g1, uint8_t g2, uint8_t g3)
|
|
{
|
|
uint8_t c1 = ((g3 & 4) << 1) | ((g2 & 4) << 0) | ((g1 & 4) >> 1) | ((g0 & 4) >> 2);
|
|
uint8_t c2 = ((g3 & 2) << 2) | ((g2 & 2) << 1) | ((g1 & 2) << 0) | ((g0 & 2) >> 1);
|
|
uint8_t c3 = ((g3 & 1) << 3) | ((g2 & 1) << 2) | ((g1 & 1) << 1) | ((g0 & 1) << 0);
|
|
|
|
access_bit_buffer[0] = (~c2 & 0xF) << 4 | (~c1 & 0xF);
|
|
access_bit_buffer[1] = c1 << 4 | (~c3 & 0xF);
|
|
access_bit_buffer[2] = c3 << 4 | c2;
|
|
}
|
|
|
|
int mfrc522_mifare_open_uid_backdoor(mfrc522_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
/* Magic sequence:
|
|
* > 50 00 57 CD (HALT + CRC)
|
|
* > 40 (7 bits only)
|
|
* < A (4 bits only)
|
|
* > 43
|
|
* < A (4 bits only)
|
|
* Then you can write to sector 0 without authenticating */
|
|
|
|
/* 50 00 57 CD */
|
|
mfrc522_picc_halt_a(dev);
|
|
|
|
uint8_t cmd = 0x40;
|
|
|
|
/* Our command is only 7 bits. After receiving card response,
|
|
* this will contain amount of valid response bits. */
|
|
uint8_t valid_bits = 7;
|
|
|
|
/* Card's response is written here */
|
|
uint8_t response[32];
|
|
uint8_t received = sizeof(response);
|
|
|
|
/* 40 */
|
|
int rc = mfrc522_pcd_transceive_data(dev, &cmd, 1, response, &received, &valid_bits, 0, false);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
if (received != 1 || response[0] != 0x0A) {
|
|
return -EIO;
|
|
}
|
|
|
|
cmd = 0x43;
|
|
valid_bits = 8;
|
|
|
|
/* 43 */
|
|
rc = mfrc522_pcd_transceive_data(dev, &cmd, 1, response, &received, &valid_bits, 0, false);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
if (received != 1 || response[0] != 0x0A) {
|
|
return -EIO;
|
|
}
|
|
|
|
/* You can now write to sector 0 without authenticating! */
|
|
return 0;
|
|
}
|
|
|
|
int mfrc522_mifare_set_uid(mfrc522_t *dev, mfrc522_uid_t *uid,
|
|
const uint8_t *new_uid, uint8_t new_uid_size)
|
|
{
|
|
assert(dev);
|
|
|
|
/* UID + BCC byte can not be larger than 15 together */
|
|
if (!new_uid || !new_uid_size || new_uid_size > 15) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Authenticate for reading */
|
|
mfrc522_mifare_key_t key = { .key_byte = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } };
|
|
|
|
int rc = mfrc522_pcd_authenticate(dev, MFRC522_PICC_CMD_MF_AUTH_KEY_A, 1, &key, uid);
|
|
|
|
if (rc != 0) {
|
|
|
|
if (rc == -ETIMEDOUT) {
|
|
/* We get a read timeout if no card is selected yet, so let's select one */
|
|
|
|
/* Wake the card up again if sleeping
|
|
* uint8_t atqa_answer[2];
|
|
* uint8_t atqa_size = 2;
|
|
* mfrc522_picc_wakeup_a(atqa_answer, &atqa_size); */
|
|
|
|
if (!mfrc522_picc_is_new_card_present(dev)) {
|
|
return -EIO;
|
|
}
|
|
|
|
rc = mfrc522_picc_read_card_serial(dev, uid);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = mfrc522_pcd_authenticate(dev, MFRC522_PICC_CMD_MF_AUTH_KEY_A, 1, &key, uid);
|
|
|
|
if (rc != 0) {
|
|
/* We tried, time to give up */
|
|
return rc;
|
|
}
|
|
}
|
|
else {
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* Read block 0 */
|
|
uint8_t block0_buffer[18];
|
|
uint8_t byte_count = sizeof(block0_buffer);
|
|
|
|
rc = mfrc522_mifare_read(dev, 0, block0_buffer, &byte_count);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
/* Write new UID to the data we just read, and calculate BCC byte */
|
|
uint8_t bcc = 0;
|
|
for (uint8_t i = 0; i < new_uid_size; i++) {
|
|
block0_buffer[i] = new_uid[i];
|
|
bcc ^= new_uid[i];
|
|
}
|
|
|
|
block0_buffer[new_uid_size] = bcc;
|
|
|
|
/* Stop encrypted traffic so we can send raw bytes */
|
|
mfrc522_pcd_stop_crypto1(dev);
|
|
|
|
/* Activate UID backdoor */
|
|
rc = mfrc522_mifare_open_uid_backdoor(dev);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
/* Write modified block 0 back to card */
|
|
rc = mfrc522_mifare_write(dev, 0, block0_buffer, 16);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
/* Wake the card up again */
|
|
uint8_t atqa_answer[2];
|
|
uint8_t atqa_size = 2;
|
|
mfrc522_picc_wakeup_a(dev, atqa_answer, &atqa_size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mfrc522_mifare_unbrick_uid_sector(mfrc522_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
int rc = mfrc522_mifare_open_uid_backdoor(dev);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
uint8_t block0_buffer[] = { 0x01, 0x02, 0x03, 0x04, 0x04, 0x08, 0x04, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
|
|
/* Write modified block 0 back to card */
|
|
return mfrc522_mifare_write(dev, (uint8_t)0, block0_buffer, (uint8_t)16);
|
|
}
|
|
|
|
bool mfrc522_picc_is_new_card_present(mfrc522_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
uint8_t buffer_ATQA[2];
|
|
uint8_t buffer_size = sizeof(buffer_ATQA);
|
|
|
|
/* Reset baud rates */
|
|
_device_write(dev, MFRC522_REG_TX_MODE, 0x00);
|
|
_device_write(dev, MFRC522_REG_RX_MODE, 0x00);
|
|
|
|
/* Reset ModWidthReg */
|
|
_device_write(dev, MFRC522_REG_MOD_WIDTH, 0x26);
|
|
|
|
int rc = mfrc522_picc_request_a(dev, buffer_ATQA, &buffer_size);
|
|
|
|
return (rc == 0 || rc == -ECONNABORTED);
|
|
}
|
|
|
|
int mfrc522_picc_read_card_serial(mfrc522_t *dev, mfrc522_uid_t *uid)
|
|
{
|
|
assert(dev);
|
|
|
|
return mfrc522_picc_select(dev, uid, 0);
|
|
}
|
|
|
|
void mfrc522_pcd_dump_version_to_serial(mfrc522_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
/* Get the MFRC522 firmware version */
|
|
uint8_t version;
|
|
_device_read(dev, MFRC522_REG_VERSION, &version);
|
|
printf("0x%x", version);
|
|
|
|
/* Lookup which version */
|
|
switch (version) {
|
|
case 0x88:
|
|
printf(" = (clone)");
|
|
break;
|
|
case 0x90:
|
|
printf(" = v0.0");
|
|
break;
|
|
case 0x91:
|
|
printf(" = v1.0");
|
|
break;
|
|
case 0x92:
|
|
printf(" = v2.0");
|
|
break;
|
|
case 0x12:
|
|
printf(" = counterfeit chip");
|
|
break;
|
|
default:
|
|
printf(" = (unknown)");
|
|
break;
|
|
}
|
|
|
|
/* When 0x00 or 0xFF is returned, communication probably failed */
|
|
if ((version == 0x00) || (version == 0xFF)) {
|
|
printf("WARNING: Communication failure, is the MFRC522 properly connected?");
|
|
}
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
void mfrc522_picc_dump_to_serial(mfrc522_t *dev, mfrc522_uid_t *uid)
|
|
{
|
|
assert(dev);
|
|
|
|
mfrc522_mifare_key_t key;
|
|
|
|
/* Dump UID, SAK and Type */
|
|
mfrc522_picc_dump_details_to_serial(uid);
|
|
|
|
/* Dump contents */
|
|
mfrc522_picc_type_t picc_type = mfrc522_picc_get_type(uid->sak);
|
|
|
|
switch (picc_type) {
|
|
case MFRC522_PICC_TYPE_MIFARE_MINI:
|
|
/* fall through */
|
|
case MFRC522_PICC_TYPE_MIFARE_1K:
|
|
/* fall through */
|
|
case MFRC522_PICC_TYPE_MIFARE_4K:
|
|
/* All keys are set to FFFFFFFFFFFFh at chip delivery from the factory */
|
|
for (uint8_t i = 0; i < 6; i++) {
|
|
key.key_byte[i] = 0xFF;
|
|
}
|
|
mfrc522_picc_dump_mifare_classic_to_serial(dev, uid, picc_type, &key);
|
|
break;
|
|
|
|
case MFRC522_PICC_TYPE_MIFARE_UL:
|
|
mfrc522_picc_dump_mifare_ultralight_to_serial(dev);
|
|
break;
|
|
|
|
case MFRC522_PICC_TYPE_ISO_14443_4:
|
|
/* fall through */
|
|
case MFRC522_PICC_TYPE_MIFARE_DESFIRE:
|
|
/* fall through */
|
|
case MFRC522_PICC_TYPE_ISO_18092:
|
|
/* fall through */
|
|
case MFRC522_PICC_TYPE_MIFARE_PLUS:
|
|
/* fall through */
|
|
case MFRC522_PICC_TYPE_TNP3XXX:
|
|
printf("Dumping memory contents not implemented for that PICC type.");
|
|
break;
|
|
|
|
case MFRC522_PICC_TYPE_UNKNOWN:
|
|
/* fall through */
|
|
case MFRC522_PICC_TYPE_NOT_COMPLETE:
|
|
/* fall through */
|
|
default:
|
|
/* No memory dump here */
|
|
break;
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
/* Already done if it was a MIFARE Classic PICC */
|
|
mfrc522_picc_halt_a(dev);
|
|
}
|
|
|
|
void mfrc522_picc_dump_details_to_serial(mfrc522_uid_t *uid)
|
|
{
|
|
printf("Card UID:");
|
|
for (uint8_t i = 0; i < uid->size; i++) {
|
|
if (uid->uid_byte[i] < 0x10) {
|
|
printf(" 0");
|
|
}
|
|
else {
|
|
printf(" ");
|
|
}
|
|
printf("%x", uid->uid_byte[i]);
|
|
}
|
|
printf("\n");
|
|
|
|
printf("Card SAK: ");
|
|
if (uid->sak < 0x10) {
|
|
printf("0");
|
|
}
|
|
printf("%x\n", uid->sak);
|
|
|
|
/* (suggested) PICC type */
|
|
mfrc522_picc_type_t picc_type = mfrc522_picc_get_type(uid->sak);
|
|
printf("PICC type: %s\n", mfrc522_picc_get_type_string(picc_type));
|
|
}
|
|
|
|
void mfrc522_picc_dump_mifare_classic_to_serial(mfrc522_t *dev, mfrc522_uid_t *uid,
|
|
mfrc522_picc_type_t picc_type,
|
|
mfrc522_mifare_key_t *key)
|
|
{
|
|
assert(dev);
|
|
|
|
uint8_t no_of_sectors = 0;
|
|
|
|
switch (picc_type) {
|
|
case MFRC522_PICC_TYPE_MIFARE_MINI:
|
|
/* Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes */
|
|
no_of_sectors = 5;
|
|
break;
|
|
|
|
case MFRC522_PICC_TYPE_MIFARE_1K:
|
|
/* Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes */
|
|
no_of_sectors = 16;
|
|
break;
|
|
|
|
case MFRC522_PICC_TYPE_MIFARE_4K:
|
|
/* Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16 bytes/block = 4096 bytes */
|
|
no_of_sectors = 40;
|
|
break;
|
|
|
|
default:
|
|
/* Should not happen. Ignore */
|
|
break;
|
|
}
|
|
|
|
/* Dump sectors, highest address first */
|
|
if (no_of_sectors) {
|
|
printf("Sector Block 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 AccessBits\n");
|
|
for (int8_t i = no_of_sectors - 1; i >= 0; i--) {
|
|
mfrc522_picc_dump_mifare_classic_sector_to_serial(dev, uid, key, i);
|
|
}
|
|
}
|
|
|
|
/* Halt the PICC before stopping the encrypted session */
|
|
mfrc522_picc_halt_a(dev);
|
|
mfrc522_pcd_stop_crypto1(dev);
|
|
}
|
|
|
|
void mfrc522_picc_dump_mifare_classic_sector_to_serial(mfrc522_t *dev, mfrc522_uid_t *uid,
|
|
mfrc522_mifare_key_t *key, uint8_t sector)
|
|
{
|
|
assert(dev);
|
|
|
|
int rc;
|
|
|
|
/* Address of lowest address to dump actually last block dumped */
|
|
uint8_t first_block;
|
|
/* Number of blocks in sector */
|
|
uint8_t no_of_blocks;
|
|
/* Set to true while handling the "last" (i.e. highest address) in the sector */
|
|
bool is_sector_trailer;
|
|
|
|
/* The access bits are stored in a peculiar fashion.
|
|
* There are four groups:
|
|
* g[3] Access bits for the sector trailer, block 3 (for sectors 0-31) or block 15 (for sectors 32-39)
|
|
* g[2] Access bits for block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39)
|
|
* g[1] Access bits for block 1 (for sectors 0-31) or blocks 5-9 (for sectors 32-39)
|
|
* g[0] Access bits for block 0 (for sectors 0-31) or blocks 0-4 (for sectors 32-39)
|
|
* Each group has access bits [C1 C2 C3]. In this code C1 is MSB and C3 is LSB.
|
|
* The four CX bits are stored together in a nible cx and an inverted nible cx_ */
|
|
|
|
/* Nibbles */
|
|
uint8_t c1, c2, c3;
|
|
|
|
/* Inverted nibbles */
|
|
uint8_t c1_, c2_, c3_;
|
|
|
|
/* True if one of the inverted nibbles did not match */
|
|
bool inverted_error = false;
|
|
|
|
/* Access bits for each of the four groups */
|
|
uint8_t g[4];
|
|
|
|
/* 0-3 - active group for access bits */
|
|
uint8_t group;
|
|
|
|
/* True for the first block dumped in the group */
|
|
bool first_in_group;
|
|
|
|
/* Determine position and size of sector */
|
|
if (sector < 32) {
|
|
/* Sectors 0..31 has 4 blocks each */
|
|
no_of_blocks = 4;
|
|
first_block = sector * no_of_blocks;
|
|
}
|
|
else if (sector < 40) {
|
|
/* Sectors 32-39 has 16 blocks each */
|
|
no_of_blocks = 16;
|
|
first_block = 128 + (sector - 32) * no_of_blocks;
|
|
}
|
|
else {
|
|
/* Illegal input, no MIFARE Classic PICC has more than 40 sectors */
|
|
return;
|
|
}
|
|
|
|
/* Dump blocks, highest address first */
|
|
uint8_t byte_count;
|
|
uint8_t buffer[18];
|
|
is_sector_trailer = true;
|
|
|
|
for (int8_t blockOffset = no_of_blocks - 1; blockOffset >= 0; blockOffset--) {
|
|
uint8_t block_addr = first_block + blockOffset;
|
|
|
|
/* Sector number - only on first line */
|
|
if (is_sector_trailer) {
|
|
if (sector < 10) {
|
|
/* Pad with spaces */
|
|
printf(" ");
|
|
}
|
|
else {
|
|
/* Pad with spaces */
|
|
printf(" ");
|
|
}
|
|
printf("%d", sector);
|
|
/* Pad with spaces */
|
|
printf(" ");
|
|
}
|
|
else {
|
|
/* Pad with spaces */
|
|
printf(" ");
|
|
}
|
|
|
|
/* Block number */
|
|
if (block_addr < 10) {
|
|
/* Pad with spaces */
|
|
printf(" ");
|
|
}
|
|
else {
|
|
if (block_addr < 100) {
|
|
/* Pad with spaces */
|
|
printf(" ");
|
|
}
|
|
else {
|
|
/* Pad with spaces */
|
|
printf(" ");
|
|
}
|
|
}
|
|
printf("%d", block_addr);
|
|
printf(" ");
|
|
|
|
/* Establish encrypted communications before reading the first block */
|
|
if (is_sector_trailer) {
|
|
rc = mfrc522_pcd_authenticate(dev, MFRC522_PICC_CMD_MF_AUTH_KEY_A, first_block, key, uid);
|
|
if (rc != 0) {
|
|
printf("mfrc522_pcd_authenticate() failed: %d\n", rc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Read block */
|
|
byte_count = sizeof(buffer);
|
|
|
|
rc = mfrc522_mifare_read(dev, block_addr, buffer, &byte_count);
|
|
if (rc != 0) {
|
|
printf("mfrc522_mifare_read() failed: %d\n", rc);
|
|
continue;
|
|
}
|
|
|
|
/* Dump data */
|
|
for (uint8_t index = 0; index < 16; index++) {
|
|
|
|
if (buffer[index] < 0x10) {
|
|
printf(" 0");
|
|
}
|
|
else {
|
|
printf(" ");
|
|
}
|
|
|
|
printf("%x", buffer[index]);
|
|
if ((index % 4) == 3) {
|
|
printf(" ");
|
|
}
|
|
}
|
|
|
|
/* Parse sector trailer data */
|
|
if (is_sector_trailer) {
|
|
c1 = buffer[7] >> 4;
|
|
c2 = buffer[8] & 0xF;
|
|
c3 = buffer[8] >> 4;
|
|
c1_ = buffer[6] & 0xF;
|
|
c2_ = buffer[6] >> 4;
|
|
c3_ = buffer[7] & 0xF;
|
|
inverted_error = (c1 != (~c1_ & 0xF)) || (c2 != (~c2_ & 0xF)) || (c3 != (~c3_ & 0xF));
|
|
g[0] = ((c1 & 1) << 2) | ((c2 & 1) << 1) | ((c3 & 1) << 0);
|
|
g[1] = ((c1 & 2) << 1) | ((c2 & 2) << 0) | ((c3 & 2) >> 1);
|
|
g[2] = ((c1 & 4) << 0) | ((c2 & 4) >> 1) | ((c3 & 4) >> 2);
|
|
g[3] = ((c1 & 8) >> 1) | ((c2 & 8) >> 2) | ((c3 & 8) >> 3);
|
|
is_sector_trailer = false;
|
|
}
|
|
|
|
/* Which access group is this block in? */
|
|
if (no_of_blocks == 4) {
|
|
group = blockOffset;
|
|
first_in_group = true;
|
|
}
|
|
else {
|
|
group = blockOffset / 5;
|
|
first_in_group = (group == 3) || (group != (blockOffset + 1) / 5);
|
|
}
|
|
|
|
if (first_in_group) {
|
|
/* Print access bits */
|
|
printf(" [ ");
|
|
printf("%d", (g[group] >> 2) & 1);
|
|
printf(" ");
|
|
printf("%d", (g[group] >> 1) & 1);
|
|
printf(" ");
|
|
printf("%d", (g[group] >> 0) & 1);
|
|
printf(" ] ");
|
|
if (inverted_error) {
|
|
printf(" Inverted access bits did not match! ");
|
|
}
|
|
}
|
|
|
|
/* Not a sector trailer, a value block */
|
|
if (group != 3 && (g[group] == 1 || g[group] == 6)) {
|
|
uint32_t value = (((uint32_t)buffer[3]) << 24)
|
|
| (((uint32_t)buffer[2]) << 16)
|
|
| (((uint32_t)buffer[1]) << 8)
|
|
| ((uint32_t)buffer[0]);
|
|
printf(" Value=0x");
|
|
printf("%lx", ((unsigned long)value));
|
|
printf(" Addr=0x");
|
|
printf("%x", buffer[12]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void mfrc522_picc_dump_mifare_ultralight_to_serial(mfrc522_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
int rc;
|
|
uint8_t byte_count;
|
|
uint8_t buffer[18];
|
|
uint8_t i;
|
|
|
|
printf("Page 0 1 2 3\n");
|
|
/* Try the mpages of the original Ultralight. Ultralight C has more pages. */
|
|
/* Read returns data for 4 pages at a time */
|
|
for (uint8_t page = 0; page < 16; page += 4) {
|
|
/* Read pages */
|
|
byte_count = sizeof(buffer);
|
|
|
|
rc = mfrc522_mifare_read(dev, page, buffer, &byte_count);
|
|
if (rc != 0) {
|
|
printf("mfrc522_mifare_read() failed: %d\n", rc);
|
|
break;
|
|
}
|
|
|
|
/* Dump data */
|
|
for (uint8_t offset = 0; offset < 4; offset++) {
|
|
i = page + offset;
|
|
if (i < 10) {
|
|
/* Pad with spaces */
|
|
printf(" ");
|
|
}
|
|
else {
|
|
/* Pad with spaces */
|
|
printf(" ");
|
|
}
|
|
|
|
printf("%d", i);
|
|
printf(" ");
|
|
for (uint8_t index = 0; index < 4; index++) {
|
|
i = 4 * offset + index;
|
|
|
|
if (buffer[i] < 0x10) {
|
|
printf(" 0");
|
|
}
|
|
else {
|
|
printf(" ");
|
|
}
|
|
|
|
printf("%x", buffer[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Firmware data for self-test
|
|
* Reference values based on firmware version */
|
|
|
|
/* Version 0.0 (0x90)
|
|
* Philips Semiconductors; Preliminary Specification Revision 2.0 - 01 August 2005; 16.1 self-test
|
|
*/
|
|
const uint8_t MFRC522_FIRMWARE_REFERENCEV0_0[] = {
|
|
0x00, 0x87, 0x98, 0x0f, 0x49, 0xFF, 0x07, 0x19,
|
|
0xBF, 0x22, 0x30, 0x49, 0x59, 0x63, 0xAD, 0xCA,
|
|
0x7F, 0xE3, 0x4E, 0x03, 0x5C, 0x4E, 0x49, 0x50,
|
|
0x47, 0x9A, 0x37, 0x61, 0xE7, 0xE2, 0xC6, 0x2E,
|
|
0x75, 0x5A, 0xED, 0x04, 0x3D, 0x02, 0x4B, 0x78,
|
|
0x32, 0xFF, 0x58, 0x3B, 0x7C, 0xE9, 0x00, 0x94,
|
|
0xB4, 0x4A, 0x59, 0x5B, 0xFD, 0xC9, 0x29, 0xDF,
|
|
0x35, 0x96, 0x98, 0x9E, 0x4F, 0x30, 0x32, 0x8D
|
|
};
|
|
|
|
/* Version 1.0 (0x91)
|
|
* NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 self-test
|
|
*/
|
|
const uint8_t MFRC522_FIRMWARE_REFERENCEV1_0[] = {
|
|
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
|
|
};
|
|
|
|
/* Version 2.0 (0x92)
|
|
* NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 self-test
|
|
*/
|
|
const uint8_t MFRC522_FIRMWARE_REFERENCEV2_0[] = {
|
|
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
|
|
};
|
|
|
|
/* Clone
|
|
* Fudan Semiconductor FM17522 (0x88)
|
|
*/
|
|
const uint8_t MFRC522_FM17522_FIRMWARE_REFERENCE[] = {
|
|
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
|
|
};
|
|
|
|
bool mfrc522_pcd_perform_self_test(mfrc522_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
/* This follows directly the steps outlined in 16.1.1 */
|
|
/* 1. Perform a soft reset */
|
|
mfrc522_pcd_reset(dev);
|
|
|
|
/* 2. Clear the internal buffer by writing 25 bytes of 00h */
|
|
uint8_t ZEROES[25] = { 0 };
|
|
|
|
/* flush the FIFO buffer */
|
|
_device_write(dev, MFRC522_REG_FIFO_LEVEL, 0x80);
|
|
|
|
/* write 25 bytes of 00h to FIFO */
|
|
_device_write_n(dev, MFRC522_REG_FIFO_DATA, 25, ZEROES);
|
|
|
|
/* transfer to internal buffer */
|
|
_device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_MEM);
|
|
|
|
/* 3. Enable self-test */
|
|
_device_write(dev, MFRC522_REG_AUTO_TEST, 0x09);
|
|
|
|
/* 4. Write 00h to FIFO buffer */
|
|
_device_write(dev, MFRC522_REG_FIFO_DATA, 0x00);
|
|
|
|
/* 5. Start self-test by issuing the CalcCRC command */
|
|
_device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_CALC_CRC);
|
|
|
|
/* 6. Wait for self-test to complete */
|
|
for (uint8_t i = 0; i < 0xFF; i++) {
|
|
/* The datasheet does not specify exact completion condition except
|
|
* that FIFO buffer should contain 64 bytes. While selftest is initiated
|
|
* by CalcCRC command it behaves differently from normal CRC computation,
|
|
* so one can't reliably use DivIrqReg to check for completion. It is
|
|
* reported that some devices does not trigger CRCIRq flag during selftest. */
|
|
|
|
uint8_t n;
|
|
_device_read(dev, MFRC522_REG_FIFO_LEVEL, &n);
|
|
if (n >= 64) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Stop calculating CRC for new content in the FIFO */
|
|
_device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_IDLE);
|
|
|
|
/* 7. Read out resulting 64 bytes from the FIFO buffer */
|
|
uint8_t result[64];
|
|
_device_read_n(dev, MFRC522_REG_FIFO_DATA, 64, result, 0);
|
|
|
|
/* Auto self-test done */
|
|
/* Reset AutoTestReg register to be 0 again. Required for normal operation. */
|
|
_device_write(dev, MFRC522_REG_AUTO_TEST, 0x00);
|
|
|
|
/* Determine firmware version (see section 9.3.4.8 in spec) */
|
|
uint8_t version;
|
|
_device_read(dev, MFRC522_REG_VERSION, &version);
|
|
|
|
/* Pick the appropriate reference values */
|
|
const uint8_t *reference;
|
|
switch (version) {
|
|
/* Fudan Semiconductor FM17522 clone */
|
|
case 0x88:
|
|
reference = MFRC522_FM17522_FIRMWARE_REFERENCE;
|
|
break;
|
|
/* Version 0.0 */
|
|
case 0x90:
|
|
reference = MFRC522_FIRMWARE_REFERENCEV0_0;
|
|
break;
|
|
/* Version 1.0 */
|
|
case 0x91:
|
|
reference = MFRC522_FIRMWARE_REFERENCEV1_0;
|
|
break;
|
|
/* Version 2.0 */
|
|
case 0x92:
|
|
reference = MFRC522_FIRMWARE_REFERENCEV2_0;
|
|
break;
|
|
/* Unknown version */
|
|
default:
|
|
/* abort test */
|
|
return false;
|
|
}
|
|
|
|
/* Verify that the results match up to our expectations */
|
|
for (uint8_t i = 0; i < 64; i++) {
|
|
if (result[i] != reference[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Test passed; all is good */
|
|
return true;
|
|
}
|