libnfc/libnfc/drivers/rc522_uart.c
2015-06-20 16:59:50 +02:00

374 lines
10 KiB
C

/*-
* 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 <http://www.gnu.org/licenses/>
*/
/**
* @file rc522_uart.c
* @brief Driver for MFRC522- and FM17222-based devices connected with an UART
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "rc522_uart.h"
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <assert.h>
#include <nfc/nfc.h>
#include "drivers.h"
#include "nfc-internal.h"
#include "chips/rc522.h"
#include "uart.h"
#define LOG_CATEGORY "libnfc.driver.rc522_uart"
#define LOG_GROUP NFC_LOG_GROUP_DRIVER
#define BOOT_BAUD_RATE 9600
#define DEFAULT_BAUD_RATE 115200
#define DRIVER_NAME "rc522_uart"
#define IO_TIMEOUT 50
#define DRIVER_DATA(pnd) ((struct rc522_uart_data*)(pnd->driver_data))
#define CHK(x) ret = (x); if (ret < 0) { return ret; }
// Internal data structs
const struct rc522_io rc522_uart_io;
struct rc522_uart_data {
serial_port port;
uint32_t baudrate;
};
/*
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 };
ret = uart_send(DRIVER_DATA(pnd)->port, rc522_wakeup_preamble, sizeof(rc522_wakeup_preamble), IO_TIMEOUT);
if (ret < 0) {
return ret;
}
return rc522_wait_wakeup(pnd);
}
*/
void rc522_uart_close(nfc_device * pnd) {
rc522_powerdown(pnd);
// Release UART port
uart_close(DRIVER_DATA(pnd)->port);
rc522_data_free(pnd);
nfc_device_free(pnd);
}
bool rc522_uart_test_baudrate(struct nfc_device * pnd, uint32_t baudrate) {
int ret;
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Attempting to establish a connection at %d bps.", baudrate);
// Update UART baudrate
if ((ret = uart_set_speed(DRIVER_DATA(pnd)->port, baudrate)) < 0) {
return false;
}
// Attempt to test and initialize the device
if (rc522_init(pnd) != NFC_SUCCESS) {
return false;
}
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Connection with a RC522 at %d bps established successfully.", baudrate);
return true;
}
int rc522_uart_create(const nfc_context * context, const nfc_connstring connstring, const char * portPath, uint32_t userBaudRate, struct nfc_device ** pndPtr) {
int ret;
serial_port sp;
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Attempt to open: %s.", portPath);
sp = uart_open(portPath);
if (sp == INVALID_SERIAL_PORT) {
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Invalid serial port: %s", portPath);
return NFC_EIO;
}
if (sp == CLAIMED_SERIAL_PORT) {
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_ERROR, "Serial port already claimed: %s", portPath);
return NFC_EIO;
}
// We need to flush input to be sure first reply does not comes from older byte transceive
if ((ret = uart_flush_input(sp, true)) < 0) {
return ret;
}
nfc_device * pnd = nfc_device_new(context, connstring);
if (!pnd) {
perror("nfc_device_new");
uart_close(sp);
return NFC_ESOFT;
}
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);
return NFC_ESOFT;
}
DRIVER_DATA(pnd)->port = sp;
DRIVER_DATA(pnd)->baudrate = userBaudRate;
// 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);
return NFC_ESOFT;
}
// Here we'll have to address several posibilities:
// - The hard reset trick did the work, and the RC522 is up and listening at 9600
// - The hard reset didn't work, but the RC522 hasn't been used yet and therefore listens at 9600
// - The hard reset didn't work and the RC522 is not using the default, so we'll use the custom provided baud rate
// Let's try first with boot baud rate
if (
!rc522_uart_test_baudrate(pnd, BOOT_BAUD_RATE) &&
!rc522_uart_test_baudrate(pnd, userBaudRate)
) {
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Could not connect with RC522 at %d or %d bps.", BOOT_BAUD_RATE, userBaudRate);
rc522_uart_close(pnd);
return NFC_EIO;
}
*pndPtr = pnd;
return NFC_SUCCESS;
}
size_t rc522_uart_scan(const nfc_context * context, nfc_connstring connstrings[], const size_t connstrings_len) {
size_t device_found = 0;
char ** acPorts = uart_list_ports();
const char * acPort;
size_t iDevice = 0;
while ((acPort = acPorts[iDevice++])) {
nfc_connstring connstring;
snprintf(connstring, sizeof(nfc_connstring), "%s:%s:%"PRIu32, DRIVER_NAME, acPort, DEFAULT_BAUD_RATE);
nfc_device * pnd;
int ret = rc522_uart_create(context, connstring, acPort, DEFAULT_BAUD_RATE, &pnd);
if (ret == NFC_ESOFT) {
uart_list_free(acPorts);
return 0;
}
if (ret != NFC_SUCCESS) {
continue;
}
rc522_uart_close(pnd);
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 = NULL;
char * baud_str = NULL;
uint32_t baudrate;
char * endptr;
struct nfc_device * pnd = NULL;
int decodelvl = connstring_decode(connstring, DRIVER_NAME, NULL, &port_str, &baud_str);
switch (decodelvl) {
case 2: // Got port but no speed
baudrate = DEFAULT_BAUD_RATE;
break;
case 3: // Got port and baud rate
// TODO: set baud rate AFTER initialization
baudrate = (uint32_t) strtol(baud_str, &endptr, 10);
if (*endptr != '\0') {
free(port_str);
free(baud_str);
return NULL;
}
free(baud_str);
break;
default: // Got unparseable gibberish
free(port_str);
free(baud_str);
return NULL;
}
rc522_uart_create(context, connstring, port_str, baudrate, &pnd);
free(port_str);
return pnd;
}
#define READ 1
#define WRITE 0
uint8_t rc522_uart_pack(int reg, int op) {
assert(reg < 64);
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) {
if ((ret = uart_send(DRIVER_DATA(pnd)->port, &cmd, 1, IO_TIMEOUT)) < 0) {
goto error;
}
if ((ret = uart_receive(DRIVER_DATA(pnd)->port, data, 1, NULL, IO_TIMEOUT)) < 0) {
goto error;
}
size--;
data++;
}
return NFC_SUCCESS;
error:
uart_flush_input(DRIVER_DATA(pnd)->port, true);
return ret;
}
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);
int ret;
while (size > 0) {
// First: send write request
if ((ret = uart_send(DRIVER_DATA(pnd)->port, &cmd, 1, IO_TIMEOUT)) < 0) {
goto error;
}
// Second: wait for a reply
uint8_t reply;
if ((ret = uart_receive(DRIVER_DATA(pnd)->port, &reply, 1, NULL, IO_TIMEOUT)) < 0) {
return ret;
}
// Third: compare sent and received. They must match.
if (cmd != reply) {
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "rc522_uart_write ack does not match (sent %02X, received %02X)", cmd, reply);
ret = NFC_ECHIP;
goto error;
}
// Fourth: send register data
if ((ret = uart_send(DRIVER_DATA(pnd)->port, data, 1, IO_TIMEOUT)) < 0) {
goto error;
}
size--;
data++;
}
return NFC_SUCCESS;
error:
uart_flush_input(DRIVER_DATA(pnd)->port, true);
return ret;
}
int rc522_uart_reset_baud_rate(struct nfc_device * pnd) {
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Restoring baud rate to default of %d bps.", BOOT_BAUD_RATE);
return uart_set_speed(DRIVER_DATA(pnd)->port, BOOT_BAUD_RATE);
}
int rc522_uart_upgrade_baud_rate(struct nfc_device * pnd) {
int ret;
uint32_t userBaudRate = DRIVER_DATA(pnd)->baudrate;
if (userBaudRate == BOOT_BAUD_RATE) {
return NFC_SUCCESS;
}
log_put(LOG_GROUP, LOG_CATEGORY, NFC_LOG_PRIORITY_DEBUG, "Upgrading baud rate to user-specified %d bps.", userBaudRate);
CHK(uart_set_speed(DRIVER_DATA(pnd)->port, userBaudRate));
CHK(rc522_send_baudrate(pnd, userBaudRate));
return NFC_SUCCESS;
}
const struct rc522_io rc522_uart_io = {
.read = rc522_uart_read,
.write = rc522_uart_write,
.reset_baud_rate = rc522_uart_reset_baud_rate,
.upgrade_baud_rate = rc522_uart_upgrade_baud_rate,
};
const struct nfc_driver rc522_uart_driver = {
.name = DRIVER_NAME,
.scan_type = INTRUSIVE,
.scan = rc522_uart_scan,
.open = rc522_uart_open,
.close = rc522_uart_close,
// .strerror = rc522_strerror,
.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_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,
// MFRC522 is unable to work as target
.target_init = NULL,
.target_send_bytes = NULL,
.target_receive_bytes = NULL,
.target_send_bits = NULL,
.target_receive_bits = NULL,
.device_set_property_bool = rc522_set_property_bool,
.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,
.abort_command = rc522_abort,
// .idle = rc522_idle,
.powerdown = rc522_powerdown,
};