/*-
* Free/Libre Near Field Communication (NFC) library
*
* Libnfc historical contributors:
* Copyright (C) 2009 Roel Verdult
* Copyright (C) 2009-2013 Romuald Conty
* Copyright (C) 2010-2012 Romain Tartière
* Copyright (C) 2010-2013 Philippe Teuwen
* Copyright (C) 2012-2013 Ludovic Rousseau
* See AUTHORS file for a more comprehensive list of contributors.
* Additional contributors of this file:
* Copyright (C) 2011 Anugrah Redja Kusuma
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see
*/
/**
* @file acr122s.c
* @brief Driver for ACS ACR122S devices
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "acr122s.h"
#include
#include
#include
#include
#include
#include
#include "drivers.h"
#include "nfc-internal.h"
#include "chips/pn53x.h"
#include "chips/pn53x-internal.h"
#include "uart.h"
#define ACR122S_DEFAULT_SPEED 9600
#define ACR122S_DRIVER_NAME "ACR122S"
#define LOG_CATEGORY "libnfc.driver.acr122s"
#define LOG_GROUP NFC_LOG_GROUP_DRIVER
// Internal data structs
struct acr122s_data {
serial_port port;
uint8_t seq;
#ifndef WIN32
int abort_fds[2];
#else
volatile bool abort_flag;
#endif
};
const struct pn53x_io acr122s_io;
#define STX 2
#define ETX 3
#define APDU_SIZE(p) ((uint32_t) (p[2] | p[3] << 8 | p[4] << 16 | p[5] << 24))
#define FRAME_OVERHEAD 13
#define FRAME_SIZE(p) (APDU_SIZE(p) + FRAME_OVERHEAD)
#define MAX_FRAME_SIZE (FRAME_OVERHEAD + 5 + 255)
enum {
ICC_POWER_ON_REQ_MSG = 0x62,
ICC_POWER_OFF_REQ_MSG = 0x63,
XFR_BLOCK_REQ_MSG = 0x6F,
ICC_POWER_ON_RES_MSG = 0x80,
ICC_POWER_OFF_RES_MSG = 0x81,
XFR_BLOCK_RES_MSG = 0x80,
};
enum {
POWER_AUTO = 0,
POWER_5_0_V = 1,
POWER_3_0_V = 2,
POWER_1_8_V = 3,
};
#pragma pack(push, 1)
struct icc_power_on_req {
uint8_t message_type;
uint32_t length;
uint8_t slot;
uint8_t seq;
uint8_t power_select;
uint8_t rfu[2];
};
struct icc_power_on_res {
uint8_t message_type;
uint32_t length;
uint8_t slot;
uint8_t seq;
uint8_t status;
uint8_t error;
uint8_t chain_parameter;
};
struct icc_power_off_req {
uint8_t message_type;
uint32_t length;
uint8_t slot;
uint8_t seq;
uint8_t rfu[3];
};
struct icc_power_off_res {
uint8_t message_type;
uint32_t length;
uint8_t slot;
uint8_t seq;
uint8_t status;
uint8_t error;
uint8_t clock_status;
};
struct xfr_block_req {
uint8_t message_type;
uint32_t length;
uint8_t slot;
uint8_t seq;
uint8_t bwi;
uint8_t rfu[2];
};
struct xfr_block_res {
uint8_t message_type;
uint32_t length;
uint8_t slot;
uint8_t seq;
uint8_t status;
uint8_t error;
uint8_t chain_parameter;
};
struct apdu_header {
uint8_t class;
uint8_t ins;
uint8_t p1;
uint8_t p2;
uint8_t length;
};
#pragma pack(pop)
#define DRIVER_DATA(pnd) ((struct acr122s_data *) (pnd->driver_data))
/**
* Fix a command frame with a valid prefix, checksum, and suffix.
*
* @param frame is command frame to fix
* @note command frame length (uint32_t at offset 2) should be valid
*/
static void
acr122s_fix_frame(uint8_t *frame)
{
size_t frame_size = FRAME_SIZE(frame);
frame[0] = STX;
frame[frame_size - 1] = ETX;
uint8_t *csum = frame + frame_size - 2;
*csum = 0;
for (uint8_t *p = frame + 1; p < csum; p++)
*csum ^= *p;
}
/**
* Send a command frame to ACR122S and check its ACK status.
*
* @param: pnd is target nfc device
* @param: cmd is command frame to send
* @param: timeout
* @return 0 if success
*/
static int
acr122s_send_frame(nfc_device *pnd, uint8_t *frame, int timeout)
{
size_t frame_size = FRAME_SIZE(frame);
uint8_t ack[4];
uint8_t positive_ack[4] = { STX, 0, 0, ETX };
serial_port port = DRIVER_DATA(pnd)->port;
int ret;
void *abort_p;
#ifndef WIN32
abort_p = &(DRIVER_DATA(pnd)->abort_fds[1]);
#else
abort_p = &(DRIVER_DATA(pnd)->abort_flag);
#endif
if ((ret = uart_send(port, frame, frame_size, timeout)) < 0)
return ret;
if ((ret = uart_receive(port, ack, 4, abort_p, timeout)) < 0)
return ret;
if (memcmp(ack, positive_ack, 4) != 0) {
pnd->last_error = NFC_EIO;
return pnd->last_error;
}
struct xfr_block_req *req = (struct xfr_block_req *) &frame[1];
DRIVER_DATA(pnd)->seq = req->seq + 1;
return 0;
}
/**
* Receive response frame after a successfull acr122s_send_command().
*
* @param: pnd is target nfc device
* @param: frame is buffer where received response frame will be stored
* @param: frame_size is frame size
* @param: abort_p
* @param: timeout
* @note returned frame size can be fetched using FRAME_SIZE macro
*
* @return 0 if success
*/
static int
acr122s_recv_frame(nfc_device *pnd, uint8_t *frame, size_t frame_size, void *abort_p, int timeout)
{
if (frame_size < 13) {
pnd->last_error = NFC_EINVARG;
return pnd->last_error;
}
int ret;
serial_port port = DRIVER_DATA(pnd)->port;
if ((ret = uart_receive(port, frame, 11, abort_p, timeout)) != 0)
return ret;
// Is buffer sufficient to store response?
if (frame_size < FRAME_SIZE(frame)) {
pnd->last_error = NFC_EIO;
return pnd->last_error;
}
size_t remaining = FRAME_SIZE(frame) - 11;
if ((ret = uart_receive(port, frame + 11, remaining, abort_p, timeout)) != 0)
return ret;
struct xfr_block_res *res = (struct xfr_block_res *) &frame[1];
if ((uint8_t)(res->seq + 1) != DRIVER_DATA(pnd)->seq) {
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Invalid response sequence number.");
pnd->last_error = NFC_EIO;
return pnd->last_error;
}
return 0;
}
#define APDU_OVERHEAD (FRAME_OVERHEAD + 5)
/**
* Convert host uint32 to litle endian uint32
*/
static uint32_t
le32(uint32_t val)
{
uint32_t res;
uint8_t *p = (uint8_t *) &res;
p[0] = val;
p[1] = val >> 8;
p[2] = val >> 16;
p[3] = val >> 24;
return res;
}
/**
* Build an ACR122S command frame from a PN532 command.
*
* @param pnd is device for which the command frame will be generated
* @param frame is where the resulting command frame will be generated
* @param frame_size is the passed command frame size
* @param p1
* @param p2
* @param data is PN532 APDU data without the direction prefix (0xD4)
* @param data_size is APDU data size
* @param should_prefix 1 if prefix 0xD4 should be inserted before APDU data, 0 if not
*
* @return true if frame built successfully
*/
static bool
acr122s_build_frame(nfc_device *pnd,
uint8_t *frame, size_t frame_size, uint8_t p1, uint8_t p2,
const uint8_t *data, size_t data_size, int should_prefix)
{
if (frame_size < data_size + APDU_OVERHEAD + should_prefix)
return false;
if (data_size + should_prefix > 255)
return false;
if (data == NULL)
return false;
struct xfr_block_req *req = (struct xfr_block_req *) &frame[1];
req->message_type = XFR_BLOCK_REQ_MSG;
req->length = le32(5 + data_size + should_prefix);
req->slot = 0;
req->seq = DRIVER_DATA(pnd)->seq;
req->bwi = 0;
req->rfu[0] = 0;
req->rfu[1] = 0;
struct apdu_header *header = (struct apdu_header *) &frame[11];
header->class = 0xff;
header->ins = 0;
header->p1 = p1;
header->p2 = p2;
header->length = data_size + should_prefix;
uint8_t *buf = (uint8_t *) &frame[16];
if (should_prefix)
*buf++ = 0xD4;
memcpy(buf, data, data_size);
acr122s_fix_frame(frame);
return true;
}
static int
acr122s_activate_sam(nfc_device *pnd)
{
uint8_t cmd[13];
memset(cmd, 0, sizeof(cmd));
cmd[1] = ICC_POWER_ON_REQ_MSG;
acr122s_fix_frame(cmd);
uint8_t resp[MAX_FRAME_SIZE];
int ret;
if ((ret = acr122s_send_frame(pnd, cmd, 0)) != 0)
return ret;
if ((ret = acr122s_recv_frame(pnd, resp, MAX_FRAME_SIZE, 0, 0)) != 0)
return ret;
CHIP_DATA(pnd)->power_mode = NORMAL;
return 0;
}
static int
acr122s_deactivate_sam(nfc_device *pnd)
{
uint8_t cmd[13];
memset(cmd, 0, sizeof(cmd));
cmd[1] = ICC_POWER_OFF_REQ_MSG;
acr122s_fix_frame(cmd);
uint8_t resp[MAX_FRAME_SIZE];
int ret;
if ((ret = acr122s_send_frame(pnd, cmd, 0)) != 0)
return ret;
if ((ret = acr122s_recv_frame(pnd, resp, MAX_FRAME_SIZE, 0, 0)) != 0)
return ret;
CHIP_DATA(pnd)->power_mode = LOWVBAT;
return 0;
}
static int
acr122s_get_firmware_version(nfc_device *pnd, char *version, size_t length)
{
int ret;
uint8_t cmd[MAX_FRAME_SIZE];
if (! acr122s_build_frame(pnd, cmd, sizeof(cmd), 0x48, 0, NULL, 0, 0)) {
return NFC_EINVARG;
}
if ((ret = acr122s_send_frame(pnd, cmd, 1000)) != 0)
return ret;
if ((ret = acr122s_recv_frame(pnd, cmd, sizeof(cmd), 0, 0)) != 0)
return ret;
size_t len = APDU_SIZE(cmd);
if (len + 1 > length)
len = length - 1;
memcpy(version, cmd + 11, len);
version[len] = 0;
return 0;
}
struct acr122s_descriptor {
char *port;
uint32_t speed;
};
static size_t
acr122s_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 ACR122S device on serial port: %s at %d baud.", acPort, ACR122S_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);
uart_set_speed(sp, ACR122S_DEFAULT_SPEED);
nfc_connstring connstring;
snprintf(connstring, sizeof(nfc_connstring), "%s:%s:%"PRIu32, ACR122S_DRIVER_NAME, acPort, ACR122S_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 = &acr122s_driver;
pnd->driver_data = malloc(sizeof(struct acr122s_data));
if (!pnd->driver_data) {
perror("malloc");
uart_close(sp);
nfc_device_free(pnd);
iDevice = 0;
while ((acPort = acPorts[iDevice++])) {
free((void *)acPort);
}
free(acPorts);
return 0;
}
DRIVER_DATA(pnd)->port = sp;
DRIVER_DATA(pnd)->seq = 0;
#ifndef WIN32
if (pipe(DRIVER_DATA(pnd)->abort_fds) < 0) {
uart_close(DRIVER_DATA(pnd)->port);
nfc_device_free(pnd);
iDevice = 0;
while ((acPort = acPorts[iDevice++])) {
free((void *)acPort);
}
free(acPorts);
return 0;
}
#else
DRIVER_DATA(pnd)->abort_flag = false;
#endif
if (pn53x_data_new(pnd, &acr122s_io) == NULL) {
perror("malloc");
uart_close(DRIVER_DATA(pnd)->port);
nfc_device_free(pnd);
iDevice = 0;
while ((acPort = acPorts[iDevice++])) {
free((void *)acPort);
}
free(acPorts);
return 0;
}
CHIP_DATA(pnd)->type = PN532;
CHIP_DATA(pnd)->power_mode = NORMAL;
char version[32];
int ret = acr122s_get_firmware_version(pnd, version, sizeof(version));
if (ret == 0 && strncmp("ACR122S", version, 7) != 0) {
ret = -1;
}
uart_close(DRIVER_DATA(pnd)->port);
pn53x_data_free(pnd);
nfc_device_free(pnd);
if (ret != 0)
continue;
// ACR122S reader is found
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;
}
static void
acr122s_close(nfc_device *pnd)
{
acr122s_deactivate_sam(pnd);
pn53x_idle(pnd);
uart_close(DRIVER_DATA(pnd)->port);
#ifndef WIN32
// Release file descriptors used for abort mechanism
close(DRIVER_DATA(pnd)->abort_fds[0]);
close(DRIVER_DATA(pnd)->abort_fds[1]);
#endif
pn53x_data_free(pnd);
nfc_device_free(pnd);
}
static nfc_device *
acr122s_open(const nfc_context *context, const nfc_connstring connstring)
{
serial_port sp;
nfc_device *pnd;
struct acr122s_descriptor ndd;
char *speed_s;
int connstring_decode_level = connstring_decode(connstring, ACR122S_DRIVER_NAME, NULL, &ndd.port, &speed_s);
if (connstring_decode_level == 3) {
ndd.speed = 0;
if (sscanf(speed_s, "%10"PRIu32, &ndd.speed) != 1) {
// speed_s is not a number
free(ndd.port);
free(speed_s);
return NULL;
}
free(speed_s);
}
if (connstring_decode_level < 2) {
return NULL;
}
if (connstring_decode_level < 3) {
ndd.speed = ACR122S_DEFAULT_SPEED;
}
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG,
"Attempt to connect to: %s at %d baud.", ndd.port, ndd.speed);
sp = uart_open(ndd.port);
if (sp == INVALID_SERIAL_PORT) {
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR,
"Invalid serial port: %s", ndd.port);
free(ndd.port);
return NULL;
}
if (sp == CLAIMED_SERIAL_PORT) {
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR,
"Serial port already claimed: %s", ndd.port);
free(ndd.port);
return NULL;
}
uart_flush_input(sp, true);
uart_set_speed(sp, ndd.speed);
pnd = nfc_device_new(context, connstring);
if (!pnd) {
perror("malloc");
free(ndd.port);
uart_close(sp);
return NULL;
}
pnd->driver = &acr122s_driver;
strcpy(pnd->name, ACR122S_DRIVER_NAME);
free(ndd.port);
pnd->driver_data = malloc(sizeof(struct acr122s_data));
if (!pnd->driver_data) {
perror("malloc");
uart_close(sp);
nfc_device_free(pnd);
return NULL;
}
DRIVER_DATA(pnd)->port = sp;
DRIVER_DATA(pnd)->seq = 0;
#ifndef WIN32
if (pipe(DRIVER_DATA(pnd)->abort_fds) < 0) {
uart_close(DRIVER_DATA(pnd)->port);
nfc_device_free(pnd);
return NULL;
}
#else
DRIVER_DATA(pnd)->abort_flag = false;
#endif
if (pn53x_data_new(pnd, &acr122s_io) == NULL) {
perror("malloc");
uart_close(DRIVER_DATA(pnd)->port);
nfc_device_free(pnd);
return NULL;
}
CHIP_DATA(pnd)->type = PN532;
#if 1
// Retrieve firmware version
char version[DEVICE_NAME_LENGTH];
if (acr122s_get_firmware_version(pnd, version, sizeof(version)) != 0) {
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Cannot get reader firmware.");
acr122s_close(pnd);
return NULL;
}
if (strncmp(version, "ACR122S", 7) != 0) {
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Invalid firmware version: %s",
version);
acr122s_close(pnd);
return NULL;
}
snprintf(pnd->name, sizeof(pnd->name), "%s", version);
// Activate SAM before operating
if (acr122s_activate_sam(pnd) != 0) {
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Cannot activate SAM.");
acr122s_close(pnd);
return NULL;
}
#endif
if (pn53x_init(pnd) < 0) {
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Failed initializing PN532 chip.");
acr122s_close(pnd);
return NULL;
}
return pnd;
}
static int
acr122s_send(nfc_device *pnd, const uint8_t *buf, const size_t buf_len, int timeout)
{
uart_flush_input(DRIVER_DATA(pnd)->port, false);
uint8_t cmd[MAX_FRAME_SIZE];
if (! acr122s_build_frame(pnd, cmd, sizeof(cmd), 0, 0, buf, buf_len, 1)) {
return NFC_EINVARG;
}
int ret;
if ((ret = acr122s_send_frame(pnd, cmd, timeout)) != 0) {
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Unable to transmit data. (TX)");
pnd->last_error = ret;
return pnd->last_error;
}
return NFC_SUCCESS;
}
static int
acr122s_receive(nfc_device *pnd, uint8_t *buf, size_t buf_len, int timeout)
{
void *abort_p;
#ifndef WIN32
abort_p = &(DRIVER_DATA(pnd)->abort_fds[1]);
#else
abort_p = &(DRIVER_DATA(pnd)->abort_flag);
#endif
uint8_t tmp[MAX_FRAME_SIZE];
pnd->last_error = acr122s_recv_frame(pnd, tmp, sizeof(tmp), abort_p, timeout);
if (abort_p && (NFC_EOPABORTED == pnd->last_error)) {
pnd->last_error = NFC_EOPABORTED;
return pnd->last_error;
}
if (pnd->last_error < 0) {
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "%s", "Unable to receive data. (RX)");
return -1;
}
size_t data_len = FRAME_SIZE(tmp) - 17;
if (data_len > buf_len) {
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Receive buffer too small. (buf_len: %" PRIuPTR ", data_len: %" PRIuPTR ")", buf_len, data_len);
pnd->last_error = NFC_EIO;
return pnd->last_error;
}
memcpy(buf, tmp + 13, data_len);
return data_len;
}
static int
acr122s_abort_command(nfc_device *pnd)
{
if (pnd) {
#ifndef WIN32
close(DRIVER_DATA(pnd)->abort_fds[0]);
close(DRIVER_DATA(pnd)->abort_fds[1]);
if (pipe(DRIVER_DATA(pnd)->abort_fds) < 0) {
return NFC_ESOFT;
}
#else
DRIVER_DATA(pnd)->abort_flag = true;
#endif
}
return NFC_SUCCESS;
}
const struct pn53x_io acr122s_io = {
.send = acr122s_send,
.receive = acr122s_receive,
};
const struct nfc_driver acr122s_driver = {
.name = ACR122S_DRIVER_NAME,
.scan_type = INTRUSIVE,
.scan = acr122s_scan,
.open = acr122s_open,
.close = acr122s_close,
.strerror = pn53x_strerror,
.initiator_init = pn53x_initiator_init,
.initiator_init_secure_element = NULL, // No secure-element support
.initiator_select_passive_target = pn53x_initiator_select_passive_target,
.initiator_poll_target = pn53x_initiator_poll_target,
.initiator_select_dep_target = pn53x_initiator_select_dep_target,
.initiator_deselect_target = pn53x_initiator_deselect_target,
.initiator_transceive_bytes = pn53x_initiator_transceive_bytes,
.initiator_transceive_bits = pn53x_initiator_transceive_bits,
.initiator_transceive_bytes_timed = pn53x_initiator_transceive_bytes_timed,
.initiator_transceive_bits_timed = pn53x_initiator_transceive_bits_timed,
.initiator_target_is_present = pn53x_initiator_target_is_present,
.target_init = pn53x_target_init,
.target_send_bytes = pn53x_target_send_bytes,
.target_receive_bytes = pn53x_target_receive_bytes,
.target_send_bits = pn53x_target_send_bits,
.target_receive_bits = pn53x_target_receive_bits,
.device_set_property_bool = pn53x_set_property_bool,
.device_set_property_int = pn53x_set_property_int,
.get_supported_modulation = pn53x_get_supported_modulation,
.get_supported_baud_rate = pn53x_get_supported_baud_rate,
.device_get_information_about = pn53x_get_information_about,
.abort_command = acr122s_abort_command,
.idle = pn53x_idle,
/* Even if PN532, PowerDown is not recommended on those devices */
.powerdown = NULL,
};